From 668e570bd1db6bd00e4293b6977e6d3d051053ce Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期四, 27 十一月 2025 00:07:46 +0800
Subject: [PATCH] feat: 修改app录入界面及车辆用户同步
---
dryad-payment/src/main/resources/mapper/NotifyLogMapper.xml | 55
ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncService.java | 9
sql/dryad_payment_tables.sql | 126
dryad-payment/src/main/resources/META-INF/spring.factories | 2
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskPayment.java | 267
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/DepartmentSyncTask.java | 24
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java | 1
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/AlipayConfig.java | 55
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/util/QrCodeUtil.java | 61
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TiandituMapServiceImpl.java | 94
sql/add_emergency_resync_flag.sql | 9
dryad-payment/src/main/java/com/ruoyi/payment/common/AjaxResult.java | 87
dryad-payment/src/main/resources/mapper/PaymentOrderMapper.xml | 77
ruoyi-ui/src/views/task/detail/index.vue | 104
app/pages/task/create-emergency.vue | 176
ruoyi-system/src/main/java/com/ruoyi/system/domain/DepartmentSyncDTO.java | 2
sql/additional_fee_sync_update.sql | 34
dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/PayChannel.java | 40
prd/分公司默认出发地配置功能说明.md | 189
dryad-payment/doc/代码生成总结.md | 268
dryad-payment/src/main/java/com/ruoyi/payment/application/service/PaymentService.java | 338 +
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java | 39
app/plugins/index.js | 3
dryad-payment/README.md | 254
dryad-payment/src/main/java/com/ruoyi/payment/domain/event/PaymentSuccessEvent.java | 50
app/pages/task/detail.vue | 218
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java | 11
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/PaymentOrderMapper.java | 40
app/pages/task/edit-emergency.vue | 554 +
app/components/OrganizationSelector.vue | 6
dryad-payment/.gitignore | 35
ruoyi-admin/src/main/resources/application.yml | 55
dryad-payment/src/main/resources/mapper/PaymentTransactionMapper.xml | 74
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskEmergencyMapper.java | 10
ruoyi-system/src/main/java/com/ruoyi/system/event/PaymentSuccessEventListener.java | 52
dryad-payment/src/main/resources/application.yml | 98
dryad-payment/src/main/java/com/ruoyi/payment/interfaces/controller/PaymentNotifyController.java | 128
sql/task_payment_method_dict.sql | 15
dryad-payment/pom.xml | 199
ruoyi-admin/src/main/resources/application-dev.yml | 2
dryad-payment/src/main/java/com/ruoyi/payment/config/PaymentAutoConfiguration.java | 48
dryad-payment/doc/支付宝第三方接口测试文档.md | 320 +
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/BizCallbackLogMapper.java | 29
ruoyi-system/src/main/resources/mapper/system/PaidMoneyAddMapper.xml | 82
dryad-payment/src/main/java/com/ruoyi/payment/domain/model/BizCallbackLog.java | 49
dryad-payment/src/main/resources/mapper/BizCallbackLogMapper.xml | 50
ruoyi-system/src/main/java/com/ruoyi/system/service/IMapService.java | 21
ruoyi-system/src/main/java/com/ruoyi/system/config/MapServiceConfiguration.java | 54
pom.xml | 1
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/OaSyncTask.java | 9
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskPaymentResultVO.java | 81
ruoyi-ui/src/api/task.js | 31
dryad-payment/API接口文档.md | 470 +
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskEmergencyService.java | 9
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/LegacySystemSyncTask.java | 111
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/alipay/AlipayUtil.java | 43
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskEmergency.java | 11
ruoyi-system/src/main/java/com/ruoyi/system/mapper/PaidMoneyMapper.java | 67
app/api/map.js | 3
ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/PaymentCallbackController.java | 75
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskAdditionalFeeMapper.java | 75
doc/转运任务支付功能设计方案.md | 172
ruoyi-system/pom.xml | 5
dryad-payment/src/main/java/com/ruoyi/payment/example/PaymentIntegrationExample.java | 140
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskPaymentService.java | 92
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacySystemSyncServiceImpl.java | 202
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PaymentModuleServiceImpl.java | 118
sql/PaidMoney.sql | 16
sql/resync_vehicle_personnel_job.sql | 96
dryad-payment/src/main/resources/sql/schema.sql | 134
dryad-payment/src/main/java/com/ruoyi/payment/domain/model/PaymentTransaction.java | 66
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskPaymentMapper.java | 108
ruoyi-system/src/main/java/com/ruoyi/system/service/ILegacySystemSyncService.java | 17
ruoyi-system/src/main/java/com/ruoyi/system/service/IPaymentSyncService.java | 44
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskEmergencyServiceImpl.java | 26
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/util/SignUtil.java | 63
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncServiceImpl.java | 70
ruoyi-system/src/main/resources/mapper/system/PaidMoneyMapper.xml | 109
app/components/DepartureSelector.vue | 419 +
dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/ClientType.java | 40
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/wechat/WxPayV2Client.java | 179
dryad-payment/src/main/java/com/ruoyi/payment/interfaces/dto/PaymentResponse.java | 33
sql/update_task_payment_20250123.sql | 8
ruoyi-common/src/main/java/com/ruoyi/common/config/LegacySystemConfig.java | 18
ruoyi-system/src/main/resources/mapper/system/SysTaskEmergencyMapper.xml | 30
dryad-payment/src/main/java/com/ruoyi/payment/interfaces/dto/PaymentRequest.java | 38
dryad-payment/src/main/java/com/ruoyi/payment/interfaces/controller/HealthController.java | 38
ruoyi-framework/src/main/java/com/ruoyi/framework/config/MybatisPlusConfig.java | 0
sql/sys_dept_add_departure_fields.sql | 14
app/components/HospitalSelector.vue | 6
ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java | 2
ruoyi-common/src/main/java/com/ruoyi/common/config/MapServiceConfig.java | 28
sql/task_payment_tables.sql | 53
app/pages/task/settlement.vue | 832 ++
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/wechat/WxPayUtil.java | 129
dryad-payment/src/main/java/com/ruoyi/payment/domain/model/NotifyLog.java | 46
dryad-payment/src/main/java/com/ruoyi/payment/domain/model/OperationAudit.java | 40
dryad-payment/src/main/java/com/ruoyi/payment/interfaces/callback/PaymentCallback.java | 32
prd/支付信息双向同步功能说明.md | 276 +
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskPaymentInfoVO.java | 158
ruoyi-system/src/main/resources/mapper/system/SysTaskPaymentMapper.xml | 156
dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/TransactionStatus.java | 46
dryad-payment/src/main/java/com/ruoyi/payment/application/service/PaymentNotifyService.java | 218
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/PaymentTransactionMapper.java | 44
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/util/SnowflakeIdGenerator.java | 96
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/OperationAuditMapper.java | 24
ruoyi-system/src/main/resources/mapper/system/DepartmentSyncMapper.xml | 18
sql/update_additional_fee_type_dict.sql | 10
ruoyi-system/src/main/java/com/ruoyi/system/domain/PaidMoneyAdd.java | 118
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/alipay/AlipayThirdPartyClient.java | 196
ruoyi-system/src/main/java/com/ruoyi/system/service/IAdditionalFeeSyncService.java | 42
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskPaymentServiceImpl.java | 415 +
app/plugins/dict.js | 7
dryad-payment/src/main/java/com/ruoyi/payment/domain/model/GoodsDetail.java | 51
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncDataServiceImpl.java | 6
dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/OrderStatus.java | 52
ruoyi-system/src/main/java/com/ruoyi/system/mapper/PaidMoneyAddMapper.java | 68
dryad-payment/src/main/java/com/ruoyi/payment/application/service/BizCallbackService.java | 247
sql/PaidMoney_Add.sql | 12
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/BusinessCallbackConfig.java | 37
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/AdditionalFeeSyncServiceImpl.java | 315 +
doc/支付功能实现进度.md | 137
ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncDataService.java | 2
sql/payment_sync_job.sql | 113
dryad-payment/src/main/java/com/ruoyi/payment/PaymentApplication.java | 29
ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml | 24
dryad-payment/doc/支付宝第三方接口开发总结.md | 248
doc/支付宝支付方式配置说明.md | 85
dryad-payment/src/main/resources/mapper/OperationAuditMapper.xml | 31
dryad-payment/doc/支付宝第三方接口-快速开始.md | 171
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/WechatPayConfig.java | 36
dryad-payment/src/main/java/com/ruoyi/payment/domain/model/PaymentOrder.java | 88
ruoyi-system/src/main/java/com/ruoyi/system/service/IPaymentModuleService.java | 36
dryad-payment/src/main/java/com/ruoyi/payment/interfaces/controller/PaymentController.java | 122
ruoyi-admin/pom.xml | 8
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/QrCodeConfig.java | 22
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/BaiduMapServiceImpl.java | 85
prd/附加费用双向同步功能说明.md | 285 +
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskPaymentController.java | 107
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/NotifyLogMapper.java | 35
dryad-payment/集成使用指南.md | 317 +
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PaymentSyncServiceImpl.java | 298 +
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAdditionalFee.java | 168
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java | 327 +
ruoyi-system/src/main/java/com/ruoyi/system/mapper/DepartmentSyncMapper.java | 8
ruoyi-system/src/main/java/com/ruoyi/system/domain/PaidMoney.java | 191
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskPaymentCreateVO.java | 56
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskUpdateVO.java | 49
sql/payment_sync_update.sql | 34
dryad-payment/doc/支付宝第三方接口使用说明.md | 199
dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/alipay/AlipayF2FClient.java | 173
dryad-payment/doc/设计方案.md | 1067 +++
app/api/payment.js | 56
ruoyi-system/src/main/resources/mapper/system/SysTaskAdditionalFeeMapper.xml | 86
154 files changed, 15,992 insertions(+), 276 deletions(-)
diff --git a/app/api/map.js b/app/api/map.js
index 163ff1d..03b2060 100644
--- a/app/api/map.js
+++ b/app/api/map.js
@@ -85,6 +85,9 @@
})
}
+// 鍒悕锛屼笌baiduGeocoding鍔熻兘鐩稿悓
+export const baiduGeocode = baiduGeocoding
+
// 鐧惧害鍦板浘璺嚎瑙勫垝API锛堣绠椾袱涓潗鏍囦箣闂寸殑椹捐溅璺濈锛�
export function baiduRouteDriving(origin, destination) {
// 鍙傛暟楠岃瘉
diff --git a/app/api/payment.js b/app/api/payment.js
new file mode 100644
index 0000000..782398b
--- /dev/null
+++ b/app/api/payment.js
@@ -0,0 +1,56 @@
+import request from '@/utils/request'
+
+/**
+ * 鑾峰彇浠诲姟鏀粯淇℃伅
+ */
+export function getPaymentInfo(taskId) {
+ return request({
+ url: '/task/payment/info',
+ method: 'get',
+ params: { taskId }
+ })
+}
+
+/**
+ * 鏂板闄勫姞璐圭敤
+ */
+export function addAdditionalFee(data) {
+ return request({
+ url: '/task/payment/additional-fee/add',
+ method: 'post',
+ data
+ })
+}
+
+/**
+ * 鍒犻櫎闄勫姞璐圭敤
+ */
+export function removeAdditionalFee(data) {
+ return request({
+ url: '/task/payment/additional-fee/remove',
+ method: 'post',
+ data
+ })
+}
+
+/**
+ * 鍒涘缓鏀粯
+ */
+export function createPayment(data) {
+ return request({
+ url: '/task/payment/create',
+ method: 'post',
+ data
+ })
+}
+
+/**
+ * 鏌ヨ鏀粯鐘舵��
+ */
+export function getPaymentStatus(taskId, paymentId) {
+ return request({
+ url: '/task/payment/status',
+ method: 'get',
+ params: { taskId, paymentId }
+ })
+}
diff --git a/app/components/DepartureSelector.vue b/app/components/DepartureSelector.vue
new file mode 100644
index 0000000..b863f91
--- /dev/null
+++ b/app/components/DepartureSelector.vue
@@ -0,0 +1,419 @@
+<template>
+ <view class="departure-selector">
+ <view class="form-item">
+ <view class="form-label" :class="{ required: required }" v-if="showLabel">{{ label }}</view>
+ <view class="departure-input-container">
+ <view class="address-input-wrapper">
+ <input
+ class="form-input departure-input"
+ :placeholder="placeholder"
+ :value="addressValue"
+ @input="onAddressInput"
+ @focus="onAddressFocus"
+ />
+ <view class="address-suggestions" v-if="showAddressSuggestions && addressSuggestions.length > 0">
+ <view
+ class="address-suggestion-item"
+ v-for="(item, index) in addressSuggestions"
+ :key="index"
+ @click="selectAddressSuggestion(item)"
+ >
+ <view class="suggestion-name">{{ item.name }}</view>
+ <view class="suggestion-address">{{ item.district }}{{ item.address }}</view>
+ </view>
+ </view>
+ </view>
+
+ <view class="current-location-btn" @click="getCurrentLocation">
+ <uni-icons type="location" size="20" color="#007AFF"></uni-icons>
+ <text>褰撳墠浣嶇疆</text>
+ </view>
+ </view>
+
+ <view class="form-tip" v-if="showTip && addressValue">
+ <text>{{ tipText }}</text>
+ </view>
+
+ <view class="coordinate-info" v-if="showCoordinates && hasCoordinates">
+ <text class="coordinate-label">GPS鍧愭爣锛�</text>
+ <text class="coordinate-value">{{ longitudeValue }}, {{ latitudeValue }}</text>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script>
+import { baiduPlaceSuggestion, reverseGeocoder } from "@/api/map"
+
+export default {
+ name: 'DepartureSelector',
+ props: {
+ // 鏍囩鏂囨湰
+ label: {
+ type: String,
+ default: '鍑哄彂鍦�'
+ },
+ // 鏄惁鏄剧ず鏍囩
+ showLabel: {
+ type: Boolean,
+ default: true
+ },
+ // 鏄惁蹇呭~
+ required: {
+ type: Boolean,
+ default: false
+ },
+ // 鍗犱綅绗�
+ placeholder: {
+ type: String,
+ default: '璇疯緭鍏ュ嚭鍙戝湴鍦板潃'
+ },
+ // 褰撳墠鍦板潃
+ address: {
+ type: String,
+ default: ''
+ },
+ // 缁忓害
+ longitude: {
+ type: [Number, String],
+ default: null
+ },
+ // 绾害
+ latitude: {
+ type: [Number, String],
+ default: null
+ },
+ // 褰撳墠鍖哄煙锛堢敤浜庡湴鍧�鎼滅储锛�
+ region: {
+ type: String,
+ default: '骞垮窞'
+ },
+ // 鏄惁鏄剧ず鎻愮ず淇℃伅
+ showTip: {
+ type: Boolean,
+ default: true
+ },
+ // 鎻愮ず鏂囨湰
+ tipText: {
+ type: String,
+ default: '鎻愮ず锛氬彲淇敼榛樿鍑哄彂鍦板湴鍧�'
+ },
+ // 鏄惁鏄剧ず鍧愭爣淇℃伅
+ showCoordinates: {
+ type: Boolean,
+ default: false
+ }
+ },
+ data() {
+ return {
+ // 鍦板潃寤鸿鐩稿叧
+ addressSuggestions: [],
+ showAddressSuggestions: false,
+ addressSearchTimer: null
+ }
+ },
+ computed: {
+ addressValue() {
+ return this.address || ''
+ },
+ longitudeValue() {
+ return this.longitude || null
+ },
+ latitudeValue() {
+ return this.latitude || null
+ },
+ hasCoordinates() {
+ return this.longitudeValue !== null && this.latitudeValue !== null
+ }
+ },
+ methods: {
+ // 鍦板潃杈撳叆
+ onAddressInput(e) {
+ const address = e.detail.value
+
+ // 瑙﹀彂鍦板潃鏇存柊浜嬩欢
+ this.$emit('update:address', address)
+ this.$emit('address-change', address)
+
+ // 闃叉姈澶勭悊鍦板潃鎼滅储
+ if (this.addressSearchTimer) {
+ clearTimeout(this.addressSearchTimer)
+ }
+
+ if (!address || address.trim() === '') {
+ this.addressSuggestions = []
+ this.showAddressSuggestions = false
+ return
+ }
+
+ this.addressSearchTimer = setTimeout(() => {
+ this.searchAddress(address)
+ }, 300)
+ },
+
+ // 鎼滅储鍦板潃寤鸿
+ searchAddress(query) {
+ baiduPlaceSuggestion(query, this.region).then(response => {
+ if (response.code === 200 && response.data) {
+ this.addressSuggestions = response.data
+ this.showAddressSuggestions = true
+ } else {
+ this.addressSuggestions = []
+ this.showAddressSuggestions = false
+ }
+ }).catch(error => {
+ console.error('鎼滅储鍦板潃澶辫触:', error)
+ this.addressSuggestions = []
+ this.showAddressSuggestions = false
+ })
+ },
+
+ // 鍦板潃杈撳叆妗嗚幏寰楃劍鐐�
+ onAddressFocus() {
+ if (this.addressValue && this.addressSuggestions.length > 0) {
+ this.showAddressSuggestions = true
+ }
+ },
+
+ // 閫夋嫨鍦板潃寤鸿
+ selectAddressSuggestion(item) {
+ const fullAddress = item.district + item.address
+
+ // 鏇存柊鍦板潃
+ this.$emit('update:address', fullAddress)
+
+ // 鏇存柊鍧愭爣
+ if (item.location) {
+ this.$emit('update:longitude', item.location.lng)
+ this.$emit('update:latitude', item.location.lat)
+ }
+
+ // 瑙﹀彂閫夋嫨浜嬩欢
+ this.$emit('address-selected', {
+ address: fullAddress,
+ longitude: item.location ? item.location.lng : null,
+ latitude: item.location ? item.location.lat : null,
+ location: item.location
+ })
+
+ this.showAddressSuggestions = false
+ this.addressSuggestions = []
+ },
+
+ // 鑾峰彇褰撳墠浣嶇疆
+ getCurrentLocation() {
+ uni.showLoading({
+ title: '鑾峰彇浣嶇疆涓�...'
+ })
+
+ // 浣跨敤uni-app鐨凣PS瀹氫綅鍔熻兘
+ uni.getLocation({
+ type: 'gcj02', // 杩斿洖鍥芥祴灞�鍧愭爣锛岄�傜敤浜庡浗鍐呭湴鍥�
+ success: (res) => {
+ console.log('鑾峰彇鍒癎PS鍧愭爣:', res)
+ const latitude = res.latitude
+ const longitude = res.longitude
+
+ // 鏇存柊GPS鍧愭爣
+ this.$emit('update:longitude', longitude)
+ this.$emit('update:latitude', latitude)
+
+ // 璋冪敤閫嗗湴鐞嗙紪鐮佹帴鍙o紝灏嗗潗鏍囪浆鎹负鍦板潃
+ reverseGeocoder(latitude, longitude)
+ .then(response => {
+ uni.hideLoading()
+
+ if (response.code === 200 && response.data) {
+ // 鑾峰彇璇︾粏鍦板潃
+ const address = response.data.address || response.data.formattedAddress || ''
+
+ // 鏇存柊鍦板潃
+ this.$emit('update:address', address)
+
+ // 瑙﹀彂浣嶇疆鑾峰彇鎴愬姛浜嬩欢
+ this.$emit('location-success', {
+ address: address,
+ longitude: longitude,
+ latitude: latitude
+ })
+
+ console.log('閫嗗湴鐞嗙紪鐮佹垚鍔�:', address)
+ this.$modal.showToast('宸茶幏鍙栧綋鍓嶄綅缃�')
+ } else {
+ console.error('閫嗗湴鐞嗙紪鐮佸け璐�:', response.msg)
+
+ // 鍗充娇鍦板潃瑙f瀽澶辫触锛屽潗鏍囧凡淇濆瓨锛岃Е鍙戜簨浠�
+ this.$emit('location-success', {
+ address: '',
+ longitude: longitude,
+ latitude: latitude
+ })
+
+ this.$modal.showToast('浣嶇疆瑙f瀽澶辫触锛岃鎵嬪姩杈撳叆鍦板潃')
+ }
+ })
+ .catch(error => {
+ uni.hideLoading()
+ console.error('閫嗗湴鐞嗙紪鐮佸け璐�:', error)
+
+ // 鍗充娇鍦板潃瑙f瀽澶辫触锛屽潗鏍囧凡淇濆瓨锛岃Е鍙戜簨浠�
+ this.$emit('location-success', {
+ address: '',
+ longitude: longitude,
+ latitude: latitude
+ })
+
+ this.$modal.showToast('浣嶇疆瑙f瀽澶辫触锛屼絾GPS鍧愭爣宸蹭繚瀛�')
+ })
+ },
+ fail: (err) => {
+ uni.hideLoading()
+ console.error('鑾峰彇浣嶇疆澶辫触:', err)
+
+ // 鎻愮ず鐢ㄦ埛鍙兘鐨勫師鍥�
+ let errorMsg = '鑾峰彇浣嶇疆澶辫触'
+ if (err.errMsg && err.errMsg.includes('auth deny')) {
+ errorMsg = '璇峰湪璁剧疆涓紑鍚綅缃潈闄�'
+ } else if (err.errMsg && err.errMsg.includes('timeout')) {
+ errorMsg = '瀹氫綅瓒呮椂锛岃绋嶅悗閲嶈瘯'
+ }
+
+ // 瑙﹀彂澶辫触浜嬩欢
+ this.$emit('location-error', err)
+
+ this.$modal.showToast(errorMsg)
+ }
+ })
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.departure-selector {
+ .form-item {
+ margin-bottom: 40rpx;
+
+ .form-label {
+ font-size: 28rpx;
+ margin-bottom: 15rpx;
+ color: #333;
+
+ &.required::before {
+ content: '*';
+ color: #ff0000;
+ margin-right: 5rpx;
+ }
+ }
+
+ .departure-input-container {
+ display: flex;
+ align-items: flex-start;
+ gap: 15rpx;
+
+ .address-input-wrapper {
+ flex: 1;
+ position: relative;
+
+ .departure-input {
+ width: 100%;
+ height: 70rpx;
+ padding: 0 20rpx;
+ border: 1rpx solid #eee;
+ border-radius: 10rpx;
+ font-size: 28rpx;
+ box-sizing: border-box;
+ }
+
+ .address-suggestions {
+ position: absolute;
+ top: 75rpx;
+ left: 0;
+ right: 0;
+ max-height: 400rpx;
+ overflow-y: auto;
+ background-color: white;
+ border: 1rpx solid #eee;
+ border-radius: 10rpx;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+ z-index: 100;
+
+ .address-suggestion-item {
+ padding: 20rpx;
+ border-bottom: 1rpx solid #f0f0f0;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ &:active {
+ background-color: #f5f5f5;
+ }
+
+ .suggestion-name {
+ font-size: 28rpx;
+ color: #333;
+ margin-bottom: 8rpx;
+ }
+
+ .suggestion-address {
+ font-size: 24rpx;
+ color: #999;
+ }
+ }
+ }
+ }
+
+ .current-location-btn {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 10rpx 20rpx;
+ background-color: #f0f7ff;
+ border-radius: 10rpx;
+ white-space: nowrap;
+ min-height: 70rpx;
+
+ &:active {
+ background-color: #e0f0ff;
+ }
+
+ text {
+ font-size: 22rpx;
+ color: #007AFF;
+ margin-top: 4rpx;
+ }
+ }
+ }
+
+ .form-tip {
+ margin-top: 10rpx;
+ font-size: 24rpx;
+ color: #999;
+ line-height: 1.5;
+ }
+
+ .coordinate-info {
+ margin-top: 10rpx;
+ padding: 10rpx 15rpx;
+ background-color: #f0f7ff;
+ border-radius: 8rpx;
+ display: flex;
+ align-items: center;
+
+ .coordinate-label {
+ font-size: 24rpx;
+ color: #666;
+ margin-right: 10rpx;
+ }
+
+ .coordinate-value {
+ font-size: 24rpx;
+ color: #007AFF;
+ font-family: monospace;
+ }
+ }
+ }
+}
+</style>
diff --git a/app/components/HospitalSelector.vue b/app/components/HospitalSelector.vue
index 77ff0f3..5f3c420 100644
--- a/app/components/HospitalSelector.vue
+++ b/app/components/HospitalSelector.vue
@@ -312,6 +312,8 @@
this.$emit('change', hospitalData)
},
+
+
// 鍚堝苟鍖婚櫌鍦板潃锛堢渷 + 甯� + 鍖� + 璇︾粏鍦板潃锛�
buildFullAddress(hospital) {
const parts = []
@@ -385,10 +387,6 @@
this.$emit('input', {
...this.value,
address: fullAddress
- })
- this.$emit('address-selected', {
- address: fullAddress,
- location: item.location
})
this.showAddressSuggestions = false
diff --git a/app/components/OrganizationSelector.vue b/app/components/OrganizationSelector.vue
index 9371f8f..531aaf9 100644
--- a/app/components/OrganizationSelector.vue
+++ b/app/components/OrganizationSelector.vue
@@ -158,7 +158,11 @@
deptId: organization.deptId,
deptName: organization.deptName,
serviceOrderClass: organization.serviceOrderClass || '',
- region: region
+ region: region,
+ // 鍑哄彂鍦颁俊鎭�
+ departureAddress: organization.departureAddress || '',
+ departureLongitude: organization.departureLongitude || null,
+ departureLatitude: organization.departureLatitude || null
})
}
},
diff --git a/app/pages/task/create-emergency.vue b/app/pages/task/create-emergency.vue
index ef16aec..2a6c730 100644
--- a/app/pages/task/create-emergency.vue
+++ b/app/pages/task/create-emergency.vue
@@ -30,6 +30,16 @@
/>
</view>
+ <DepartureSelector
+ :address.sync="departureAddress"
+ :longitude.sync="departureLongitude"
+ :latitude.sync="departureLatitude"
+ :region="selectedRegion"
+ :required="false"
+ @address-selected="onDepartureAddressSelected"
+ @location-success="onDepartureLocationSuccess"
+ />
+
<view class="form-item">
<view class="form-label required">浠诲姟绫诲瀷</view>
<picker mode="selector" :range="emergencyTaskTypeOptions" range-key="text" @change="onEmergencyTaskTypeChange">
@@ -321,8 +331,9 @@
import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue'
import { addTask } from "@/api/task"
import { listAvailableVehicles, getUserBoundVehicle } from "@/api/vehicle"
+import { searchHospitals, searchHospitalsByDeptRegion } from "@/api/hospital"
+import DepartureSelector from '@/components/DepartureSelector.vue'
import { calculateDistance, baiduDistanceByAddress, baiduPlaceSuggestion } from "@/api/map"
-import { searchHospitals, getFrequentOutHospitals, getFrequentInHospitals, searchHospitalsByDeptRegion } from "@/api/hospital"
import { listBranchUsers } from "@/api/system/user"
import { searchIcd10 } from "@/api/icd10"
import { calculateTransferPrice } from "@/api/price"
@@ -343,7 +354,8 @@
MapSelector,
OrganizationSelector,
HospitalSelector,
- DiseaseSelector
+ DiseaseSelector,
+ DepartureSelector
},
data() {
return {
@@ -352,6 +364,9 @@
selectedOrganizationId: null, // 褰掑睘鏈烘瀯ID锛堥儴闂↖D锛�
selectedOrganizationServiceOrderClass: '', // 褰掑睘鏈烘瀯鐨勬湇鍔″崟缂栫爜
selectedRegion: '', // 浠庡綊灞炴満鏋勪腑鎻愬彇鐨勫湴鍩熶俊鎭紙濡傦細骞垮窞銆佹繁鍦崇瓑锛�
+ departureAddress: '', // 鍑哄彂鍦板湴鍧�
+ departureLongitude: null, // 鍑哄彂鍦扮粡搴�
+ departureLatitude: null, // 鍑哄彂鍦扮含搴�
selectedEmergencyTaskType: '', // 閫変腑鐨勪换鍔$被鍨嬫枃鏈�
selectedEmergencyTaskTypeId: null, // 閫変腑鐨勪换鍔$被鍨婭D
selectedDocumentType: '', // 閫変腑鐨勫崟鎹被鍨嬫枃鏈�
@@ -403,10 +418,6 @@
documentTypeOptions: [], // 鍗曟嵁绫诲瀷閫夐」锛堢敤浜巔icker鏄剧ず锛�
departmentOptions: [], // 绉戝瀛楀吀鏁版嵁
loading: false,
- addressCoordinates: {
- hospitalOutAddress: null,
- hospitalInAddress: null
- },
// 鏅鸿兘璇嗗埆鐩稿叧
rawText: '',
parseLoading: false
@@ -517,10 +528,18 @@
},
onOrganizationChange(orgData) {
- // orgData 鍖呭惈锛歞eptId, deptName, serviceOrderClass, region
+ // orgData 鍖呭惈锛歞eptId, deptName, serviceOrderClass, region, departureAddress, departureLongitude, departureLatitude
this.selectedOrganizationId = orgData.deptId
this.selectedOrganizationServiceOrderClass = orgData.serviceOrderClass
this.selectedRegion = orgData.region
+
+ // 鑷姩濉厖鍑哄彂鍦颁俊鎭紙鏈烘瀯鐨勫湴鍧�鍜屽潗鏍囷級
+ if (orgData.departureAddress) {
+ this.departureAddress = orgData.departureAddress
+ this.departureLongitude = orgData.departureLongitude || null
+ this.departureLatitude = orgData.departureLatitude || null
+ console.log('鑷姩濉厖鏈烘瀯鍑哄彂鍦�:', this.departureAddress, '鍧愭爣:', this.departureLongitude, this.departureLatitude)
+ }
console.log('閫変腑褰掑睘鏈烘瀯:', orgData.deptName, '閮ㄩ棬ID:', orgData.deptId, '鏈嶅姟鍗曠紪鐮�:', orgData.serviceOrderClass, '鍦板煙:', orgData.region)
},
@@ -635,16 +654,10 @@
}
},
- // 杞嚭鍖婚櫌鍦板潃閫夋嫨锛堝綋閫夋嫨"瀹朵腑"鏃朵娇鐢ㄧ櫨搴﹀湴鍥惧湴鍧�寤鸿锛�
+ // 杞嚭鍖婚櫌鍦板潃閫夋嫨浜嬩欢锛堢畝鍖栵紝绉婚櫎GPS澶勭悊锛�
onHospitalOutAddressSelected(data) {
- // data 鍖呭惈锛歛ddress, location
- if (data.location) {
- this.addressCoordinates.hospitalOutAddress = data.location
-
- // 濡傛灉杞叆鍦板潃涔熷凡濉啓,鑷姩璁$畻璺濈
- if (this.taskForm.hospitalIn.address) {
- this.calculateDistanceByManualAddress()
- }
+ if (this.taskForm.hospitalIn.address) {
+ this.calculateDistanceByManualAddress()
}
},
@@ -665,16 +678,10 @@
}
},
- // 杞叆鍖婚櫌鍦板潃閫夋嫨锛堝綋閫夋嫨"瀹朵腑"鏃朵娇鐢ㄧ櫨搴﹀湴鍥惧湴鍧�寤鸿锛�
+ // 杞叆鍖婚櫌鍦板潃閫夋嫨浜嬩欢锛堢畝鍖栵紝绉婚櫎GPS澶勭悊锛�
onHospitalInAddressSelected(data) {
- // data 鍖呭惈锛歛ddress, location
- if (data.location) {
- this.addressCoordinates.hospitalInAddress = data.location
-
- // 濡傛灉杞嚭鍦板潃涔熷凡濉啓,鑷姩璁$畻璺濈
- if (this.taskForm.hospitalOut.address) {
- this.calculateDistanceByManualAddress()
- }
+ if (this.taskForm.hospitalOut.address) {
+ this.calculateDistanceByManualAddress()
}
},
@@ -1072,7 +1079,7 @@
// 鐥呮儏ID鍒楄〃锛堢敤浜庡悓姝ヨ皟搴﹀崟鐨凮rdICD_ID鍙傛暟锛�
diseaseIds: this.selectedDiseases.filter(d => d.id !== null).map(d => d.id),
// 灏嗚浆鍑哄尰闄㈠湴鍧�浣滀负鍑哄彂鍦帮紝杞叆鍖婚櫌鍦板潃浣滀负鐩殑鍦�
- departureAddress: this.taskForm.hospitalOut.address || '',
+ departureAddress: this.departureAddress || this.taskForm.hospitalOut.address || '',
destinationAddress: this.taskForm.hospitalIn.address || '',
patient: {
...this.taskForm.patient,
@@ -1085,34 +1092,27 @@
icdName: d.icdName
}))
},
- // 鍖婚櫌淇℃伅锛堝寘鍚尰闄D銆佺瀹ゅ悕绉般�佺瀹D绛夊畬鏁翠俊鎭級
- hospitalOut: this.taskForm.hospitalOut, // 鍖呭惈: id, name, department, departmentId, bedNumber, address
- hospitalIn: this.taskForm.hospitalIn, // 鍖呭惈: id, name, department, departmentId, bedNumber, address
+ // 鍖婚櫌淇℃伅锛堝寘鍚尰闄D銆佺瀹ゅ悕绉般�佺瀹D銆丟PS鍧愭爣绛夊畬鏁翠俊鎭級
+ hospitalOut: {
+ ...this.taskForm.hospitalOut
+ // GPS鍧愭爣鐢卞悗绔嚜鍔ㄨ幏鍙�
+ },
+ hospitalIn: {
+ ...this.taskForm.hospitalIn
+ // GPS鍧愭爣鐢卞悗绔嚜鍔ㄨ幏鍙�
+ },
+
transferDistance: this.taskForm.transferDistance ? parseFloat(this.taskForm.transferDistance) : null,
price: this.taskForm.price ? parseFloat(this.taskForm.price) : null
}
- if (this.addressCoordinates.hospitalOutAddress) {
- // 杞嚭鍖婚櫌GPS鍧愭爣鍐欏叆鎵╁睍琛�
- if (!submitData.hospitalOut) submitData.hospitalOut = {}
- submitData.hospitalOut.longitude = this.addressCoordinates.hospitalOutAddress.lng
- submitData.hospitalOut.latitude = this.addressCoordinates.hospitalOutAddress.lat
-
- // 鍚屾椂鍐欏叆涓讳换鍔¤〃鐨勫嚭鍙戝湴缁忕含搴�
- submitData.departureLongitude = this.addressCoordinates.hospitalOutAddress.lng
- submitData.departureLatitude = this.addressCoordinates.hospitalOutAddress.lat
+ // 鍑哄彂鍦癎PS鍧愭爣锛堜紭鍏堜娇鐢ㄨ嚜瀹氫箟鐨勫嚭鍙戝湴鍧愭爣锛�
+ if (this.departureLongitude && this.departureLatitude) {
+ submitData.departureLongitude = this.departureLongitude
+ submitData.departureLatitude = this.departureLatitude
}
- if (this.addressCoordinates.hospitalInAddress) {
- // 杞叆鍖婚櫌GPS鍧愭爣鍐欏叆鎵╁睍琛�
- if (!submitData.hospitalIn) submitData.hospitalIn = {}
- submitData.hospitalIn.longitude = this.addressCoordinates.hospitalInAddress.lng
- submitData.hospitalIn.latitude = this.addressCoordinates.hospitalInAddress.lat
-
- // 鍚屾椂鍐欏叆涓讳换鍔¤〃鐨勭洰鐨勫湴缁忕含搴�
- submitData.destinationLongitude = this.addressCoordinates.hospitalInAddress.lng
- submitData.destinationLatitude = this.addressCoordinates.hospitalInAddress.lat
- }
+ // 鐩爣鍦癎PS鍧愭爣鐢卞悗绔牴鎹浆鍏ュ尰闄㈠湴鍧�鑷姩鑾峰彇
return submitData
},
@@ -1151,6 +1151,79 @@
goBack() {
uni.navigateBack()
+ },
+
+ // 鑾峰彇褰撳墠浣嶇疆
+ getCurrentLocation() {
+ uni.showLoading({
+ title: '鑾峰彇浣嶇疆涓�...'
+ })
+
+ // 浣跨敤uni-app鐨凣PS瀹氫綅鍔熻兘
+ uni.getLocation({
+ type: 'gcj02', // 杩斿洖鍥芥祴灞�鍧愭爣锛岄�傜敤浜庡浗鍐呭湴鍥�
+ success: (res) => {
+ console.log('鑾峰彇鍒癎PS鍧愭爣:', res)
+ const latitude = res.latitude
+ const longitude = res.longitude
+
+ // 淇濆瓨GPS鍧愭爣
+ this.departureLatitude = latitude
+ this.departureLongitude = longitude
+
+ // 璋冪敤閫嗗湴鐞嗙紪鐮佹帴鍙o紝灏嗗潗鏍囪浆鎹负鍦板潃
+ reverseGeocoder(latitude, longitude)
+ .then(response => {
+ uni.hideLoading()
+
+ if (response.code === 200 && response.data) {
+ // 鑾峰彇璇︾粏鍦板潃
+ const address = response.data.address || response.data.formattedAddress || ''
+ this.departureAddress = address
+
+ console.log('閫嗗湴鐞嗙紪鐮佹垚鍔�:', address)
+ this.$modal.showToast('宸茶幏鍙栧綋鍓嶄綅缃�')
+ } else {
+ console.error('閫嗗湴鐞嗙紪鐮佸け璐�:', response.msg)
+ this.$modal.showToast('浣嶇疆瑙f瀽澶辫触锛岃鎵嬪姩杈撳叆鍦板潃')
+ }
+ })
+ .catch(error => {
+ uni.hideLoading()
+ console.error('閫嗗湴鐞嗙紪鐮佸け璐�:', error)
+ // 鍗充娇鍦板潃瑙f瀽澶辫触锛屼篃淇濈暀GPS鍧愭爣
+ this.$modal.showToast('浣嶇疆瑙f瀽澶辫触锛屼絾GPS鍧愭爣宸蹭繚瀛�')
+ })
+ },
+ fail: (err) => {
+ uni.hideLoading()
+ console.error('鑾峰彇浣嶇疆澶辫触:', err)
+
+ // 鎻愮ず鐢ㄦ埛鍙兘鐨勫師鍥�
+ let errorMsg = '鑾峰彇浣嶇疆澶辫触'
+ if (err.errMsg && err.errMsg.includes('auth deny')) {
+ errorMsg = '璇峰湪璁剧疆涓紑鍚綅缃潈闄�'
+ } else if (err.errMsg && err.errMsg.includes('timeout')) {
+ errorMsg = '瀹氫綅瓒呮椂锛岃绋嶅悗閲嶈瘯'
+ }
+
+ this.$modal.showToast(errorMsg)
+ }
+ })
+ },
+
+ // 鍑哄彂鍦板湴鍧�閫夋嫨锛堜粠鍦板浘寤鸿涓�夋嫨锛�
+ onDepartureAddressSelected(data) {
+ // data 鍖呭惈: address, longitude, latitude, location
+ console.log('鍑哄彂鍦板湴鍧�閫夋嫨:', data)
+ // 缁勪欢宸茬粡閫氳繃 .sync 鏇存柊浜� departureAddress, departureLongitude, departureLatitude
+ },
+
+ // 鍑哄彂鍦癎PS瀹氫綅鎴愬姛
+ onDepartureLocationSuccess(data) {
+ // data 鍖呭惈: address, longitude, latitude
+ console.log('鍑哄彂鍦癎PS瀹氫綅鎴愬姛:', data)
+ // 缁勪欢宸茬粡閫氳繃 .sync 鏇存柊浜� departureAddress, departureLongitude, departureLatitude
},
// ==================== 鏅鸿兘璇嗗埆鐩稿叧鏂规硶 ====================
@@ -1781,6 +1854,13 @@
font-size: 28rpx;
}
+ .form-tip {
+ margin-top: 10rpx;
+ font-size: 24rpx;
+ color: #999;
+ line-height: 1.5;
+ }
+
.disease-container {
.disease-tags {
display: flex;
diff --git a/app/pages/task/detail.vue b/app/pages/task/detail.vue
index ffd445d..fcbd8d7 100644
--- a/app/pages/task/detail.vue
+++ b/app/pages/task/detail.vue
@@ -189,8 +189,58 @@
<view class="value">{{ taskDetail.emergencyInfo.transferDistance }}鍏噷</view>
</view>
<view class="info-item" v-if="taskDetail.emergencyInfo.transferPrice">
- <view class="label">杞繍璐圭敤</view>
+ <view class="label">鍩虹璐圭敤</view>
<view class="value">锟{ taskDetail.emergencyInfo.transferPrice }}</view>
+ </view>
+ <view class="info-item" v-if="paymentInfo">
+ <view class="label">闄勫姞璐圭敤</view>
+ <view class="value">锟{ paymentInfo.additionalAmount || 0 }}</view>
+ </view>
+ <view class="info-item" v-if="paymentInfo">
+ <view class="label">鎬昏垂鐢�</view>
+ <view class="value" style="color: #e54d42; font-weight: bold;">锟{ paymentInfo.totalAmount || 0 }}</view>
+ </view>
+ <view class="info-item" v-if="paymentInfo && paymentInfo.paidAmount > 0">
+ <view class="label">宸叉敮浠�</view>
+ <view class="value" style="color: #34C759;">锟{ paymentInfo.paidAmount }}</view>
+ </view>
+ <view class="info-item" v-if="paymentInfo && paymentInfo.paidAmount > 0">
+ <view class="label">鍓╀綑鏈粯</view>
+ <view class="value" style="color: #ff9900; font-weight: bold;">锟{ getRemainingAmount() }}</view>
+ </view>
+ </view>
+
+ <!-- 鏀粯璁板綍鏄庣粏 -->
+ <view class="detail-section" v-if="paymentInfo && paymentInfo.paidPayments && paymentInfo.paidPayments.length > 0">
+ <view class="section-title">鏀粯璁板綍</view>
+ <view
+ class="payment-record-item"
+ v-for="payment in paymentInfo.paidPayments"
+ :key="payment.id"
+ >
+ <view class="payment-header">
+ <view
+ class="payment-method-tag"
+ :class="[
+ payment.paymentMethod === '1' ? 'method-cash' : '',
+ payment.paymentMethod === '2' ? 'method-bank' : '',
+ payment.paymentMethod === '3' ? 'method-wechat' : '',
+ payment.paymentMethod === '4' ? 'method-alipay' : '',
+ payment.paymentMethod === '5' ? 'method-pos' : '',
+ payment.paymentMethod === '6' ? 'method-account' : '',
+ payment.paymentMethod === '7' ? 'method-yyt' : ''
+ ]"
+ >
+ {{ getPaymentMethodLabel(payment.paymentMethod) }}
+ </view>
+ <view class="payment-amount">锟{ payment.settlementAmount }}</view>
+ </view>
+ <view class="payment-time" v-if="payment.payTime">
+ {{ formatPaymentTime(payment.payTime) }}
+ </view>
+ <view class="payment-remark" v-if="payment.remark">
+ 澶囨敞锛歿{ payment.remark }}
+ </view>
</view>
</view>
@@ -345,7 +395,16 @@
</button>
</template>
- <!-- 宸插畬鎴�/宸插彇娑�: 涓嶆樉绀烘寜閽� -->
+ <!-- 宸插畬鎴�/宸插彇娑�: 涓嶆樉绀烘寜閽紝浣嗗鏋滄槸杞繍浠诲姟鍒欐樉绀虹粨绠楁寜閽� -->
+
+ <!-- 杞繍浠诲姟鐨勭粨绠楁寜閽細浠呭畬鎴�/鍙栨秷鐘舵�佷笉鏄剧ず -->
+ <button
+ v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && !isTaskFinished"
+ class="action-btn settlement"
+ @click="handleSettlement"
+ >
+ 缁撶畻
+ </button>
</view>
</view>
</template>
@@ -353,6 +412,7 @@
<script>
import { getTask, changeTaskStatus } from '@/api/task'
import { checkVehicleActiveTasks } from '@/api/task'
+ import { getPaymentInfo } from '@/api/payment'
import { formatDateTime } from '@/utils/common'
import AttachmentUpload from '@/components/AttachmentUpload.vue'
@@ -363,7 +423,8 @@
data() {
return {
taskDetail: null,
- taskId: null
+ taskId: null,
+ paymentInfo: null // 鏀粯淇℃伅
}
},
computed: {
@@ -456,10 +517,67 @@
console.log('鍑哄彂鍦板潃:', this.taskDetail.departureAddress)
console.log('鐩殑鍦板潃:', this.taskDetail.destinationAddress)
console.log('杞繍浠诲姟淇℃伅 (emergencyInfo):', this.taskDetail.emergencyInfo)
+
+ // 濡傛灉鏄浆杩愪换鍔★紝鍔犺浇鏀粯淇℃伅
+ if (this.taskDetail.taskType === 'EMERGENCY_TRANSFER') {
+ this.loadPaymentInfo()
+ }
}).catch(error => {
console.error('鍔犺浇浠诲姟璇︽儏澶辫触:', error)
this.$modal.showToast('鍔犺浇浠诲姟璇︽儏澶辫触')
})
+ },
+
+ // 鍔犺浇鏀粯淇℃伅
+ loadPaymentInfo() {
+ getPaymentInfo(this.taskId).then(res => {
+ this.paymentInfo = res.data
+ console.log('鏀粯淇℃伅:', this.paymentInfo)
+ }).catch(err => {
+ console.error('鍔犺浇鏀粯淇℃伅澶辫触', err)
+ })
+ },
+
+ // 鏍规嵁鏀粯鏂瑰紡瀛楀吀鍊艰幏鍙栨樉绀烘爣绛�
+ getPaymentMethodLabel(dictValue) {
+ const methodMap = {
+ '1': '鐜伴噾',
+ '2': '閾惰杞处',
+ '3': '寰俊鏀粯',
+ '4': '鏀粯瀹�',
+ '5': 'POS鏀舵',
+ '6': '鎸傝处',
+ '7': '鏄撳尰閫氭寕璐�',
+ '8': '閫�娆�',
+ '9': '绉垎'
+ }
+ return methodMap[dictValue] || dictValue
+ },
+
+ // 鏍煎紡鍖栨敮浠樻椂闂�
+ formatPaymentTime(time) {
+ if (!time) return '鏈煡'
+ const date = new Date(time)
+ const year = date.getFullYear()
+ const month = String(date.getMonth() + 1).padStart(2, '0')
+ const day = String(date.getDate()).padStart(2, '0')
+ const hour = String(date.getHours()).padStart(2, '0')
+ const minute = String(date.getMinutes()).padStart(2, '0')
+ return `${year}-${month}-${day} ${hour}:${minute}`
+ },
+
+ // 璁$畻鍓╀綑鏈粯閲戦
+ getRemainingAmount() {
+ if (!this.paymentInfo) {
+ return '0.00'
+ }
+ const total = parseFloat(this.paymentInfo.totalAmount || 0)
+ const paid = parseFloat(this.paymentInfo.paidAmount || 0)
+ const remaining = total - paid
+ console.log('鎬婚噾棰�:', total.toFixed(2))
+ console.log('宸蹭粯閲戦:', paid.toFixed(2))
+ console.log('鍓╀綑鏈粯閲戦:', remaining.toFixed(2))
+ return remaining > 0 ? remaining.toFixed(2) : '0.00'
},
// 鑾峰彇杞﹁締淇℃伅
@@ -565,6 +683,13 @@
'WELFARE': '绂忕杞�'
}
return typeMap[type] || '鏈煡绫诲瀷'
+ },
+
+ // 澶勭悊缁撶畻
+ handleSettlement() {
+ uni.navigateTo({
+ url: '/pages/task/settlement?taskId=' + this.taskId
+ })
},
// 澶勭悊浠诲姟鎿嶄綔
@@ -674,10 +799,10 @@
if (!this.taskDetail) {
return null;
}
-
- // 浠庤溅杈嗗垪琛ㄤ腑鑾峰彇绗竴涓溅杈嗙殑ID
- if (this.taskDetail.vehicleList && this.taskDetail.vehicleList.length > 0) {
- return this.taskDetail.vehicleList[0].vehicleId;
+ console.log("taskDetail assignedVehicles",this.taskDetail.assignedVehicles);
+ // 浠庤溅杈嗗垪琛ㄤ腑鑾峰彇绗竴涓溅杈嗙殑ID锛堝悗绔繑鍥炵殑瀛楁鍚嶆槸assignedVehicles锛�
+ if (this.taskDetail.assignedVehicles && this.taskDetail.assignedVehicles.length > 0) {
+ return this.taskDetail.assignedVehicles[0].vehicleId;
}
// 鎴栬�呬粠鍗曚釜杞﹁締瀵硅薄鑾峰彇
@@ -1128,6 +1253,80 @@
}
}
+ // 鏀粯璁板綍鏍峰紡
+ .payment-record-item {
+ background-color: #f9f9f9;
+ border-radius: 10rpx;
+ padding: 20rpx;
+ margin-bottom: 20rpx;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ .payment-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15rpx;
+
+ .payment-method-tag {
+ display: inline-block;
+ padding: 6rpx 16rpx;
+ border-radius: 6rpx;
+ font-size: 24rpx;
+ color: white;
+ font-weight: 500;
+
+ &.method-cash {
+ background-color: #34C759;
+ }
+
+ &.method-bank {
+ background-color: #5AC8FA;
+ }
+
+ &.method-wechat {
+ background-color: #09BB07;
+ }
+
+ &.method-alipay {
+ background-color: #1677FF;
+ }
+
+ &.method-pos {
+ background-color: #FF9500;
+ }
+
+ &.method-account {
+ background-color: #FF9500;
+ }
+
+ &.method-yyt {
+ background-color: #AF52DE;
+ }
+ }
+
+ .payment-amount {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #34C759;
+ }
+ }
+
+ .payment-time {
+ font-size: 24rpx;
+ color: #999;
+ margin-bottom: 10rpx;
+ }
+
+ .payment-remark {
+ font-size: 24rpx;
+ color: #666;
+ line-height: 1.5;
+ }
+ }
+
.loading {
display: flex;
flex-direction: column;
@@ -1176,6 +1375,11 @@
color: white;
}
+ &.settlement {
+ background-color: #34C759;
+ color: white;
+ }
+
&:first-child {
margin-left: 0;
}
diff --git a/app/pages/task/edit-emergency.vue b/app/pages/task/edit-emergency.vue
index 2dd9009..3aafe37 100644
--- a/app/pages/task/edit-emergency.vue
+++ b/app/pages/task/edit-emergency.vue
@@ -37,6 +37,28 @@
/>
</view>
+ <view class="form-item">
+ <view class="form-label">鎵ц浠诲姟浜哄憳</view>
+ <view class="staff-list">
+ <view class="staff-item" v-for="(staff, index) in selectedStaff" :key="staff.userId">
+ <view class="staff-info">
+ <text class="staff-name">{{ staff.nickName }}</text>
+ <text class="staff-role">({{ getUserTypeName(staff.type) || '鏈煡鑱屼綅' }})</text>
+ </view>
+ <uni-icons
+ type="closeempty"
+ size="20"
+ color="#ff4d4f"
+ @click="removeStaff(index)"
+ ></uni-icons>
+ </view>
+ <view class="add-staff" @click="showStaffSelector">
+ <uni-icons type="plusempty" size="20" color="#007AFF"></uni-icons>
+ <text>娣诲姞浜哄憳</text>
+ </view>
+ </view>
+ </view>
+
<view class="form-section-title">鎮h�呬俊鎭�</view>
<view class="form-item">
<view class="form-label required">鑱旂郴浜�</view>
@@ -166,6 +188,80 @@
></map-selector>
</view>
</uni-popup>
+
+ <!-- 浜哄憳閫夋嫨鍣ㄥ脊绐� -->
+ <uni-popup ref="staffPopup" type="bottom" :mask-click="false">
+ <view class="staff-popup-container">
+ <view class="popup-header">
+ <view class="popup-title">閫夋嫨鎵ц浜哄憳</view>
+ <view class="close-btn" @click="closeStaffSelector">
+ <uni-icons type="closeempty" size="20" color="#999"></uni-icons>
+ </view>
+ </view>
+
+ <view class="search-bar">
+ <input
+ class="search-input"
+ placeholder="鎼滅储濮撳悕鎴栫數璇�"
+ v-model="staffSearchKeyword"
+ @input="onStaffSearch"
+ />
+ </view>
+
+ <view class="filter-tabs">
+ <view
+ class="filter-tab"
+ :class="{ active: staffFilterType === 'driver' }"
+ @click="filterStaff('driver')"
+ >
+ 鍙告満
+ </view>
+ <view
+ class="filter-tab"
+ :class="{ active: staffFilterType === 'doctor' }"
+ @click="filterStaff('doctor')"
+ >
+ 鍖荤敓
+ </view>
+ <view
+ class="filter-tab"
+ :class="{ active: staffFilterType === 'nurse' }"
+ @click="filterStaff('nurse')"
+ >
+ 鎶ゅ+
+ </view>
+ </view>
+
+ <scroll-view class="staff-list-scroll" scroll-y="true">
+ <view
+ class="staff-list-item"
+ v-for="staff in filteredStaffList"
+ :key="staff.userId"
+ @click="toggleStaffSelection(staff)"
+ >
+ <view class="staff-item-info">
+ <text class="staff-item-name">{{ staff.nickName }}</text>
+ <text class="staff-item-dept">{{ staff.deptName }}</text>
+ </view>
+ <view class="staff-item-check">
+ <uni-icons
+ v-if="isStaffSelected(staff.userId)"
+ type="checkmarkempty"
+ size="24"
+ color="#007AFF"
+ ></uni-icons>
+ </view>
+ </view>
+ <view v-if="filteredStaffList.length === 0" class="empty-tip">
+ 鏆傛棤浜哄憳鏁版嵁
+ </view>
+ </scroll-view>
+
+ <view class="popup-footer">
+ <button class="confirm-btn" @click="confirmStaffSelection">纭畾(宸查�墈{ selectedStaff.length }})</button>
+ </view>
+ </view>
+ </uni-popup>
</scroll-view>
</template>
@@ -174,6 +270,7 @@
import uniDatetimePicker from '@/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue'
import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue'
import { getTask, updateTask } from "@/api/task"
+import { listBranchUsers } from "@/api/system/user"
import { baiduDistanceByAddress } from "@/api/map"
import { calculateTransferPrice } from "@/api/price"
import MapSelector from '@/components/map-selector.vue'
@@ -196,18 +293,23 @@
mixins: [distanceCalculator],
data() {
return {
+ loading: false,
taskId: null,
taskDetail: null,
selectedVehicleId: null,
selectedOrganizationId: null,
selectedRegion: '',
mapSelectorType: '',
- // 鍦板潃鍧愭爣锛堢敤浜庢墜鍔ㄨ緭鍏ュ湴鍧�鏃惰绠楄窛绂伙級
- addressCoordinates: {
- hospitalOutAddress: null,
- hospitalInAddress: null
- },
+ // 鍑哄彂鍦颁俊鎭�
+ departureAddress: '',
+ departureLongitude: null,
+ departureLatitude: null,
selectedDiseases: [], // 宸查�夋嫨鐨勭梾鎯呭垪琛�
+ selectedStaff: [], // 宸查�夋嫨鐨勪汉鍛樺垪琛�
+ allStaffList: [], // 鎵�鏈変汉鍛樺垪琛�
+ filteredStaffList: [], // 杩囨护鍚庣殑浜哄憳鍒楄〃
+ staffSearchKeyword: '', // 浜哄憳鎼滅储鍏抽敭璇�
+ staffFilterType: 'driver', // 浜哄憳绛涢�夌被鍨�
taskForm: {
transferTime: '',
patient: {
@@ -237,8 +339,7 @@
},
transferDistance: '',
price: ''
- },
- loading: false
+ }
}
},
computed: {
@@ -255,6 +356,7 @@
if (options.id) {
this.taskId = options.id
this.loadTaskDetail()
+ this.loadDeptStaff() // 鍔犺浇浜哄憳鍒楄〃
} else {
this.$modal.showToast('浠诲姟ID涓嶈兘涓虹┖')
setTimeout(() => {
@@ -311,12 +413,30 @@
this.taskForm.hospitalOut.bedNumber = info.hospitalOutBedNumber || ''
this.taskForm.hospitalOut.address = info.hospitalOutAddress || ''
+ // 鍔犺浇杞嚭鍖婚櫌GPS鍧愭爣锛堜笉鏄剧ず锛屼絾淇濆瓨鍦ㄦ暟鎹腑锛�
+ if (info.hospitalOutLongitude && info.hospitalOutLatitude) {
+ this.addressCoordinates.hospitalOutAddress = {
+ lng: info.hospitalOutLongitude,
+ lat: info.hospitalOutLatitude
+ }
+ console.log('鍔犺浇杞嚭鍖婚櫌GPS鍧愭爣:', info.hospitalOutLongitude, info.hospitalOutLatitude)
+ }
+
// 杞叆鍖婚櫌淇℃伅
this.taskForm.hospitalIn.id = info.hospitalInId || null
this.taskForm.hospitalIn.name = info.hospitalInName || ''
this.taskForm.hospitalIn.department = info.hospitalInDepartment || ''
this.taskForm.hospitalIn.bedNumber = info.hospitalInBedNumber || ''
this.taskForm.hospitalIn.address = info.hospitalInAddress || ''
+
+ // 鍔犺浇杞叆鍖婚櫌GPS鍧愭爣锛堜笉鏄剧ず锛屼絾淇濆瓨鍦ㄦ暟鎹腑锛�
+ if (info.hospitalInLongitude && info.hospitalInLatitude) {
+ this.addressCoordinates.hospitalInAddress = {
+ lng: info.hospitalInLongitude,
+ lat: info.hospitalInLatitude
+ }
+ console.log('鍔犺浇杞叆鍖婚櫌GPS鍧愭爣:', info.hospitalInLongitude, info.hospitalInLatitude)
+ }
// 杞繍璺濈鍜屼环鏍�
this.taskForm.transferDistance = info.transferDistance ? String(info.transferDistance) : ''
@@ -346,20 +466,43 @@
console.warn('鏈壘鍒板綊灞炴満鏋勪俊鎭�')
}
- // 璁剧疆鍦板潃鍧愭爣锛堜娇鐢╩ixin涓殑鏂规硶锛�
+ // 璁剧疆鍑哄彂鍦颁俊鎭紙鍦板潃鍜屽潗鏍囷級
+ if (this.taskDetail.departureAddress) {
+ this.departureAddress = this.taskDetail.departureAddress
+ }
if (this.taskDetail.departureLongitude && this.taskDetail.departureLatitude) {
- this.setStartLocation({
- lng: this.taskDetail.departureLongitude,
- lat: this.taskDetail.departureLatitude
- })
- console.log('璁剧疆鍑哄彂鍦板潗鏍�')
+ this.departureLongitude = this.taskDetail.departureLongitude
+ this.departureLatitude = this.taskDetail.departureLatitude
+ console.log('璁剧疆鍑哄彂鍦板潗鏍�:', this.departureLongitude, this.departureLatitude)
+ }
+
+ // 璁剧疆鐩爣鍦颁俊鎭紙杞叆鍖婚櫌鐨勫湴鍧�鍜屽潗鏍囷級
+ if (this.taskDetail.destinationAddress) {
+ // 鐩爣鍦板湴鍧�宸茬粡鍦� taskForm.hospitalIn.address 涓缃�
+ console.log('鐩爣鍦板湴鍧�:', this.taskDetail.destinationAddress)
}
if (this.taskDetail.destinationLongitude && this.taskDetail.destinationLatitude) {
- this.setEndLocation({
+ this.addressCoordinates.hospitalInAddress = {
lng: this.taskDetail.destinationLongitude,
lat: this.taskDetail.destinationLatitude
- })
- console.log('璁剧疆鐩殑鍦板潗鏍�')
+ }
+ console.log('璁剧疆鐩爣鍦板潗鏍�:', this.taskDetail.destinationLongitude, this.taskDetail.destinationLatitude)
+ }
+
+ // 璁剧疆鎵ц浜哄憳
+ if (this.taskDetail.assignees && this.taskDetail.assignees.length > 0) {
+ console.log('鍘熷鎵ц浜哄憳鏁版嵁:', this.taskDetail.assignees)
+ this.selectedStaff = this.taskDetail.assignees.map(assignee => ({
+ userId: assignee.userId,
+ nickName: assignee.userName,
+ type: assignee.userType || 'driver',
+ phonenumber: '',
+ deptName: ''
+ }))
+ console.log('澶勭悊鍚庣殑鎵ц浜哄憳鍒楄〃:', this.selectedStaff)
+ } else {
+ console.warn('浠诲姟娌℃湁鍒嗛厤鎵ц浜哄憳鎴朼ssignees涓虹┖')
+ console.log('taskDetail.assignees:', this.taskDetail.assignees)
}
console.log('琛ㄥ崟鏁版嵁濉厖瀹屾垚:', this.taskForm)
@@ -402,16 +545,10 @@
}
},
- // 杞嚭鍖婚櫌鍦板潃閫夋嫨锛堝綋閫夋嫨"瀹朵腑"鏃朵娇鐢ㄧ櫨搴﹀湴鍥惧湴鍧�寤鸿锛�
+ // 杞嚭鍖婚櫌鍦板潃閫夋嫨浜嬩欢锛堢畝鍖栵級
onHospitalOutAddressSelected(data) {
- // data 鍖呭惈锛歛ddress, location
- if (data.location) {
- this.addressCoordinates.hospitalOutAddress = data.location
-
- // 濡傛灉杞叆鍦板潃涔熷凡濉啓,鑷姩璁$畻璺濈
- if (this.taskForm.hospitalIn.address) {
- this.calculateDistanceByManualAddress()
- }
+ if (this.taskForm.hospitalIn.address) {
+ this.calculateDistanceByManualAddress()
}
},
@@ -432,16 +569,10 @@
}
},
- // 杞叆鍖婚櫌鍦板潃閫夋嫨锛堝綋閫夋嫨"瀹朵腑"鏃朵娇鐢ㄧ櫨搴﹀湴鍥惧湴鍧�寤鸿锛�
+ // 杞叆鍖婚櫌鍦板潃閫夋嫨浜嬩欢锛堢畝鍖栵級
onHospitalInAddressSelected(data) {
- // data 鍖呭惈锛歛ddress, location
- if (data.location) {
- this.addressCoordinates.hospitalInAddress = data.location
-
- // 濡傛灉杞嚭鍦板潃涔熷凡濉啓,鑷姩璁$畻璺濈
- if (this.taskForm.hospitalOut.address) {
- this.calculateDistanceByManualAddress()
- }
+ if (this.taskForm.hospitalOut.address) {
+ this.calculateDistanceByManualAddress()
}
},
@@ -449,6 +580,156 @@
onDiseaseChange(diseases) {
console.log('鐥呮儏鍙樺寲:', diseases)
// 缁勪欢宸茬粡閫氳繃 v-model 鏇存柊浜� selectedDiseases
+ },
+
+ // 鍔犺浇褰撳墠鐢ㄦ埛鎵�鍦ㄥ垎鍏徃鐨勬墍鏈変汉鍛�
+ loadDeptStaff() {
+ console.log('寮�濮嬪姞杞戒汉鍛樺垪琛�')
+
+ listBranchUsers().then(response => {
+ console.log('浜哄憳鍒楄〃API鍝嶅簲:', response)
+ const userList = response.data || []
+ console.log('瑙f瀽鍑虹殑鐢ㄦ埛鍒楄〃:', userList, '鏁伴噺:', userList.length)
+
+ this.allStaffList = userList.map(user => ({
+ userId: user.userId,
+ nickName: user.nickName,
+ phonenumber: user.phonenumber,
+ deptName: user.dept?.deptName || '',
+ postName: user.posts && user.posts.length > 0 ? user.posts[0].postName : '',
+ roleName: user.roles && user.roles.length > 0 ? user.roles[0].roleName : '',
+ type: this.getUserType(user)
+ }))
+
+ console.log('澶勭悊鍚庣殑浜哄憳鍒楄〃:', this.allStaffList, '鏁伴噺:', this.allStaffList.length)
+
+ this.filterStaffList()
+ }).catch(error => {
+ console.error('鍔犺浇浜哄憳鍒楄〃澶辫触:', error)
+ this.$modal.showToast('鍔犺浇浜哄憳鍒楄〃澶辫触')
+ })
+ },
+
+ // 鏍规嵁鐢ㄦ埛鐨勫矖浣嶆垨瑙掕壊鍒ゆ柇绫诲瀷
+ getUserType(user) {
+ const postName = user.posts && user.posts.length > 0 ? user.posts[0].postName : ''
+ const roleName = user.roles && user.roles.length > 0 ? user.roles[0].roleName : ''
+ const deptName = user.dept?.deptName || ''
+
+ if (postName.includes('鍙告満') || roleName.includes('鍙告満') || deptName.includes('杞﹂槦') || deptName.includes('鍙告満')) {
+ return 'driver'
+ }
+ if (postName.includes('鎶ゅ+') || roleName.includes('鎶ゅ+') || deptName.includes('鎶ゅ+')) {
+ return 'nurse'
+ }
+ if (postName.includes('鍖荤敓') || roleName.includes('鍖荤敓') || deptName.includes('鍖荤敓')) {
+ return 'doctor'
+ }
+ if (deptName.includes('鍖绘姢')) {
+ return 'doctor'
+ }
+
+ return 'driver'
+ },
+
+ getUserTypeName(staffType) {
+ switch(staffType) {
+ case 'nurse':
+ return '鎶ゅ+'
+ case 'doctor':
+ return '鍖荤敓'
+ case 'driver':
+ return '鍙告満'
+ default:
+ return '鍙告満'
+ }
+ },
+
+ // 鏄剧ず浜哄憳閫夋嫨寮圭獥
+ showStaffSelector() {
+ this.$refs.staffPopup.open()
+ this.filterStaffList()
+ },
+
+ // 鍏抽棴浜哄憳閫夋嫨寮圭獥
+ closeStaffSelector() {
+ this.$refs.staffPopup.close()
+ this.staffSearchKeyword = ''
+ this.staffFilterType = 'driver'
+ },
+
+ // 浜哄憳鎼滅储
+ onStaffSearch(e) {
+ this.staffSearchKeyword = e.detail.value
+ this.filterStaffList()
+ },
+
+ // 绛涢�変汉鍛樼被鍨�
+ filterStaff(type) {
+ this.staffFilterType = type
+ this.filterStaffList()
+ },
+
+ // 杩囨护浜哄憳鍒楄〃
+ filterStaffList() {
+ console.log('寮�濮嬭繃婊や汉鍛樺垪琛紝鍘熷鏁伴噺:', this.allStaffList.length)
+ let list = [...this.allStaffList]
+
+ // 鎸夌被鍨嬭繃婊�
+ if (this.staffFilterType === 'driver') {
+ list = list.filter(staff => staff.type === 'driver')
+ } else if (this.staffFilterType === 'doctor') {
+ list = list.filter(staff => staff.type === 'doctor')
+ } else if (this.staffFilterType === 'nurse') {
+ list = list.filter(staff => staff.type === 'nurse')
+ }
+
+ console.log('鎸夌被鍨嬭繃婊ゅ悗:', this.staffFilterType, '鏁伴噺:', list.length)
+
+ // 鎸夊叧閿瘝鎼滅储
+ if (this.staffSearchKeyword && this.staffSearchKeyword.trim() !== '') {
+ const keyword = this.staffSearchKeyword.trim().toLowerCase()
+ list = list.filter(staff => {
+ return staff.nickName.toLowerCase().includes(keyword) ||
+ (staff.phonenumber && staff.phonenumber.includes(keyword))
+ })
+ }
+
+ console.log('鎸夊叧閿瘝杩囨护鍚庯紝鏁伴噺:', list.length)
+
+ this.filteredStaffList = list
+ },
+
+ // 鍒囨崲浜哄憳閫変腑鐘舵��
+ toggleStaffSelection(staff) {
+ const index = this.selectedStaff.findIndex(s => s.userId === staff.userId)
+
+ if (index > -1) {
+ // 宸查�変腑锛岀Щ闄�
+ this.selectedStaff.splice(index, 1)
+ } else {
+ // 鏈�変腑锛屾坊鍔�
+ this.selectedStaff.push(staff)
+ }
+ },
+
+ // 鍒ゆ柇浜哄憳鏄惁宸查�変腑
+ isStaffSelected(userId) {
+ return this.selectedStaff.some(staff => staff.userId === userId)
+ },
+
+ // 纭浜哄憳閫夋嫨
+ confirmStaffSelection() {
+ if (this.selectedStaff.length === 0) {
+ this.$modal.showToast('璇疯嚦灏戦�夋嫨涓�鍚嶄汉鍛�')
+ return
+ }
+ this.closeStaffSelector()
+ },
+
+ // 绉婚櫎浜哄憳
+ removeStaff(index) {
+ this.selectedStaff.splice(index, 1)
},
// 瑙f瀽鐥呮儏淇℃伅锛堜粠瀛楃涓茶В鏋愬嚭ICD-10鐤剧梾鍒楄〃锛�
@@ -634,10 +915,18 @@
deptId: this.selectedOrganizationId,
vehicleIds: this.selectedVehicleId ? [this.selectedVehicleId] : [],
plannedStartTime: this.taskForm.transferTime,
- departureAddress: this.taskForm.hospitalOut.address,
+ // 鍑哄彂鍦颁娇鐢� departureAddress锛堝鏋滄湁锛夛紝鍚﹀垯浣跨敤杞嚭鍖婚櫌鍦板潃
+ departureAddress: this.departureAddress || this.taskForm.hospitalOut.address,
+ // 鐩爣鍦颁娇鐢ㄨ浆鍏ュ尰闄㈠湴鍧�
destinationAddress: this.taskForm.hospitalIn.address,
// 鐥呮儏ID鍒楄〃锛堢敤浜庡悓姝ヨ皟搴﹀崟鐨凮rdICD_ID鍙傛暟锛�
diseaseIds: this.selectedDiseases.map(d => d.id).filter(id => id !== null),
+ // 鎵ц浜哄憳鍒楄〃
+ assignees: this.selectedStaff.map(staff => ({
+ userId: staff.userId,
+ userName: staff.nickName,
+ userType: staff.type
+ })),
emergencyInfo: {
patientContact: this.taskForm.patient.contact,
patientPhone: this.taskForm.patient.phone,
@@ -649,10 +938,16 @@
hospitalOutDepartment: this.taskForm.hospitalOut.department,
hospitalOutBedNumber: this.taskForm.hospitalOut.bedNumber,
hospitalOutAddress: this.taskForm.hospitalOut.address,
+ // 杞嚭鍖婚櫌GPS鍧愭爣锛堜繚瀛樺埌鎵╁睍琛級
+ hospitalOutLongitude: this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lng : null,
+ hospitalOutLatitude: this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lat : null,
hospitalInName: this.taskForm.hospitalIn.name,
hospitalInDepartment: this.taskForm.hospitalIn.department,
hospitalInBedNumber: this.taskForm.hospitalIn.bedNumber,
hospitalInAddress: this.taskForm.hospitalIn.address,
+ // 杞叆鍖婚櫌GPS鍧愭爣锛堜繚瀛樺埌鎵╁睍琛級
+ hospitalInLongitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lng : null,
+ hospitalInLatitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lat : null,
transferDistance: this.taskForm.transferDistance ? parseFloat(this.taskForm.transferDistance) : null,
transferPrice: this.taskForm.price ? parseFloat(this.taskForm.price) : null,
// 鐥呮儏璇︾粏淇℃伅锛堣繃婊ゆ帀绌虹殑鐥呮儏鍚嶇О锛�
@@ -666,16 +961,13 @@
}
}
- // 娣诲姞GPS鍧愭爣
- if (this.addressCoordinates.start) {
- submitData.departureLongitude = this.addressCoordinates.start.lng
- submitData.departureLatitude = this.addressCoordinates.start.lat
+ // 鍑哄彂鍦癎PS鍧愭爣
+ if (this.departureLongitude && this.departureLatitude) {
+ submitData.departureLongitude = this.departureLongitude
+ submitData.departureLatitude = this.departureLatitude
}
- if (this.addressCoordinates.end) {
- submitData.destinationLongitude = this.addressCoordinates.end.lng
- submitData.destinationLatitude = this.addressCoordinates.end.lat
- }
+ // 鐩爣鍦癎PS鍧愭爣鐢卞悗绔嚜鍔ㄨ幏鍙�
return submitData
},
@@ -688,8 +980,6 @@
this.$modal.confirm('纭畾瑕佷繚瀛樹慨鏀瑰悧锛�').then(() => {
this.loading = true
const submitData = this.buildSubmitData()
-
- console.log('鎻愪氦鏁版嵁:', JSON.stringify(submitData, null, 2))
updateTask(submitData).then(response => {
this.loading = false
@@ -946,6 +1236,51 @@
color: #333;
}
}
+
+ .staff-list {
+ .staff-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20rpx;
+ margin-bottom: 15rpx;
+ background-color: #f8f8f8;
+ border-radius: 10rpx;
+
+ .staff-info {
+ display: flex;
+ align-items: center;
+ gap: 10rpx;
+
+ .staff-name {
+ font-size: 28rpx;
+ color: #333;
+ }
+
+ .staff-role {
+ font-size: 24rpx;
+ color: #999;
+ }
+ }
+ }
+
+ .add-staff {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10rpx;
+ padding: 20rpx;
+ border: 2rpx dashed #007AFF;
+ border-radius: 10rpx;
+ color: #007AFF;
+ font-size: 28rpx;
+ cursor: pointer;
+
+ &:active {
+ background-color: #f0f8ff;
+ }
+ }
+ }
}
.form-actions {
@@ -1011,5 +1346,132 @@
}
}
}
+
+ .staff-popup-container {
+ height: 80vh;
+ background-color: white;
+ border-top-left-radius: 20rpx;
+ border-top-right-radius: 20rpx;
+ display: flex;
+ flex-direction: column;
+
+ .popup-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20rpx 30rpx;
+ border-bottom: 1rpx solid #f0f0f0;
+
+ .popup-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+ }
+
+ .close-btn {
+ width: 50rpx;
+ height: 50rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ }
+
+ .search-bar {
+ padding: 20rpx 30rpx;
+ border-bottom: 1rpx solid #f0f0f0;
+
+ .search-input {
+ height: 70rpx;
+ padding: 0 20rpx;
+ background-color: #f5f5f5;
+ border-radius: 10rpx;
+ font-size: 28rpx;
+ }
+ }
+
+ .filter-tabs {
+ display: flex;
+ padding: 20rpx 30rpx;
+ gap: 20rpx;
+ border-bottom: 1rpx solid #f0f0f0;
+
+ .filter-tab {
+ flex: 1;
+ height: 60rpx;
+ line-height: 60rpx;
+ text-align: center;
+ background-color: #f5f5f5;
+ border-radius: 10rpx;
+ font-size: 28rpx;
+ color: #666;
+
+ &.active {
+ background-color: #007AFF;
+ color: white;
+ }
+ }
+ }
+
+ .staff-list-scroll {
+ flex: 1;
+ padding: 20rpx 30rpx;
+
+ .staff-list-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20rpx;
+ margin-bottom: 15rpx;
+ background-color: #f8f8f8;
+ border-radius: 10rpx;
+
+ .staff-item-info {
+ flex: 1;
+
+ .staff-item-name {
+ display: block;
+ font-size: 30rpx;
+ color: #333;
+ margin-bottom: 8rpx;
+ }
+
+ .staff-item-dept {
+ display: block;
+ font-size: 24rpx;
+ color: #999;
+ }
+ }
+
+ .staff-item-check {
+ width: 50rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ }
+
+ .empty-tip {
+ text-align: center;
+ padding: 100rpx 0;
+ color: #999;
+ font-size: 28rpx;
+ }
+ }
+
+ .popup-footer {
+ padding: 20rpx 30rpx;
+ border-top: 1rpx solid #f0f0f0;
+
+ .confirm-btn {
+ width: 100%;
+ height: 80rpx;
+ background-color: #007AFF;
+ color: white;
+ border-radius: 10rpx;
+ font-size: 32rpx;
+ }
+ }
+ }
}
-</style>
+</style>
\ No newline at end of file
diff --git a/app/pages/task/settlement.vue b/app/pages/task/settlement.vue
index 9cdffdc..920ef2f 100644
--- a/app/pages/task/settlement.vue
+++ b/app/pages/task/settlement.vue
@@ -13,7 +13,7 @@
<view class="info-item">
<view class="label">浠诲姟绫诲瀷:</view>
- <view class="value">{{ getTaskTypeText(task.type) }}</view>
+ <view class="value">{{ taskTypeText }}</view>
</view>
<view class="info-item">
@@ -51,6 +51,40 @@
<view class="label">鎬昏垂鐢�:</view>
<view class="value total">楼{{ totalAmount }}</view>
</view>
+
+ <view class="amount-item paid-amount" v-if="paidAmount > 0">
+ <view class="label">宸叉敮浠�:</view>
+ <view class="value" style="color: #34C759;">楼{{ paidAmount }}</view>
+ </view>
+
+ <view class="amount-item remaining-amount" v-if="paidAmount > 0">
+ <view class="label">鍓╀綑鏈粯:</view>
+ <view class="value" style="color: #ff9900; font-weight: bold;">楼{{ remainingAmount }}</view>
+ </view>
+ </view>
+
+ <!-- 宸叉敮浠樿褰曞垪琛� -->
+ <view class="payment-history-section" v-if="paidPaymentList.length > 0">
+ <view class="section-title">鏀粯璁板綍</view>
+ <view
+ class="payment-item"
+ v-for="payment in paidPaymentList"
+ :key="payment.id"
+ >
+ <view class="payment-info">
+ <view class="payment-method">
+ <text class="method-tag" :class="['method-' + payment.paymentMethod.toLowerCase()]">
+ {{ getPaymentMethodText(payment.paymentMethod) }}
+ </text>
+ </view>
+ <view class="payment-time">
+ {{ formatPaymentTime(payment.payTime) }}
+ </view>
+ </view>
+ <view class="payment-amount">
+ 楼{{ payment.settlementAmount }}
+ </view>
+ </view>
</view>
<view class="settlement-form">
@@ -80,25 +114,11 @@
class="form-textarea"
placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�"
v-model="remark"
- />
+ ></textarea>
</view>
</view>
- <!-- 鏀粯浜岀淮鐮佸尯鍩� -->
- <view v-if="showQRCode" class="qr-code-section">
- <view class="section-title">璇锋壂鐮佹敮浠�</view>
- <view class="qr-code-container">
- <image class="qr-code" :src="qrCodeImage" mode="widthFix"></image>
- <view class="qr-tip">璇蜂娇鐢▄{ selectedPaymentMethod }}鎵爜鏀粯</view>
- </view>
- <view class="payment-status">
- <view v-if="paymentStatus === 'pending'" class="status-pending">绛夊緟鏀粯...</view>
- <view v-if="paymentStatus === 'success'" class="status-success">
- <uni-icons type="checkmarkempty" size="24" color="#4cd964"></uni-icons>
- 鏀粯鎴愬姛
- </view>
- </view>
- </view>
+
</scroll-view>
<view class="action-section">
@@ -111,6 +131,57 @@
淇濆瓨缁撶畻
</button>
</view>
+
+ <!-- 鏀粯浜岀淮鐮佸脊绐� -->
+ <uni-popup ref="qrCodePopup" type="center" :mask-click="false">
+ <view class="qr-popup-content">
+ <view class="qr-popup-header">
+ <text class="qr-popup-title">{{ selectedPaymentMethod }}鏀粯</text>
+ </view>
+
+ <view class="qr-popup-body">
+ <view class="qr-code-box" v-if="qrCodeImage">
+ <image class="qr-image" :src="qrCodeImage" mode="widthFix"></image>
+ </view>
+
+ <view class="qr-loading" v-else>
+ <uni-icons type="spinner-cycle" size="40" color="#007AFF"></uni-icons>
+ <text class="loading-text">姝e湪鐢熸垚浜岀淮鐮�...</text>
+ </view>
+
+ <view class="qr-tip-text">璇蜂娇鐢▄{ selectedPaymentMethod }}鎵爜鏀粯</view>
+ <view class="qr-amount">楼{{ settlementAmount }}</view>
+
+ <view class="payment-status-box">
+ <view v-if="paymentStatus === 'pending'" class="status-waiting">
+ <uni-icons type="spinner-cycle" size="20" color="#ff9900"></uni-icons>
+ <text>绛夊緟鏀粯涓�...</text>
+ </view>
+ <view v-if="paymentStatus === 'success'" class="status-paid">
+ <uni-icons type="checkmarkempty" size="24" color="#4cd964"></uni-icons>
+ <text>鏀粯鎴愬姛</text>
+ </view>
+ </view>
+ </view>
+
+ <view class="qr-popup-footer">
+ <button
+ class="cancel-pay-btn"
+ @click="cancelPayment"
+ v-if="paymentStatus !== 'success'"
+ >
+ 鍙栨秷鏀粯
+ </button>
+ <button
+ class="confirm-pay-btn"
+ @click="confirmPaymentSuccess"
+ v-if="paymentStatus === 'success'"
+ >
+ 纭畾
+ </button>
+ </view>
+ </view>
+ </uni-popup>
<!-- 闄勫姞璐圭敤寮圭獥 -->
<uni-popup ref="additionalFeesPopup" type="bottom">
@@ -150,18 +221,23 @@
</view>
<!-- 宸叉坊鍔犵殑璐圭敤鍒楄〃 -->
- <view class="added-fees-list" v-if="selectedAdditionalFees.length > 0">
+ <view class="added-fees-list" v-if="additionalFeeList.length > 0">
<view class="section-title">宸叉坊鍔犺垂鐢�</view>
<view
class="fee-item"
- v-for="(fee, index) in selectedAdditionalFees"
- :key="index"
+ v-for="fee in additionalFeeList"
+ :key="fee.id"
>
<view class="fee-info">
- <view class="fee-name">{{ fee.name }}</view>
- <view class="fee-price">楼{{ fee.price }} 脳 {{ fee.quantity }}</view>
+ <view class="fee-name">{{ fee.feeName }}</view>
+ <view class="fee-price">楼{{ fee.unitAmount }} 脳 {{ fee.quantity }}</view>
</view>
- <view class="fee-total">楼{{ (fee.price * fee.quantity).toFixed(2) }}</view>
+ <view class="fee-actions">
+ <view class="fee-total">楼{{ fee.totalAmount }}</view>
+ <view class="delete-btn" @click="removeFee(fee.id)" v-if="!paySuccess">
+ <uni-icons type="trash" size="20" color="#e54d42"></uni-icons>
+ </view>
+ </view>
</view>
</view>
@@ -174,103 +250,321 @@
</template>
<script>
+import { getPaymentInfo, addAdditionalFee, removeAdditionalFee, createPayment, getPaymentStatus } from '@/api/payment'
+import { getTask } from '@/api/task'
+
export default {
data() {
return {
- task: {
- id: 1,
- taskNo: 'RW20230515001',
- type: 'emergency',
- vehicle: '绮12345',
- startLocation: '骞垮窞甯傚ぉ娌冲尯XX璺�123鍙�',
- endLocation: '骞垮窞甯傜櫧浜戝尯YY璺�456鍙�'
- },
- baseAmount: 1000, // 鍩虹璐圭敤
- additionalAmount: 0, // 闄勫姞璐圭敤
+ taskId: null,
+ task: {},
+ baseAmount: 0, // 鍩虹璐圭敤(鎴愪氦浠�)
+ additionalAmount: 0, // 闄勫姞璐圭敤姹囨��
settlementAmount: '', // 缁撶畻閲戦
remark: '', // 澶囨敞
- paymentMethods: ['鐜伴噾', '鏀粯瀹�', '寰俊', '鎸傚笎'],
+ paymentMethods: [], // 浠庡瓧鍏稿姞杞芥敮浠樻柟寮�
+ paymentMethodDict: [], // 瀛楀吀鍘熷鏁版嵁
selectedPaymentMethod: '',
showQRCode: false,
paymentStatus: 'pending', // pending, success
- qrCodeImage: '/static/images/qrcode.png', // 浜岀淮鐮佸浘鐗囪矾寰�
- additionalFeeTypes: [
- { name: '鎷呮灦璐�', price: 200 },
- { name: '绛夊緟璐�', price: 100 },
- { name: '鍠勫悗璐�', price: 150 },
- { name: '澶滈棿鏈嶅姟璐�', price: 300 },
- { name: '闀块�旇垂', price: 500 }
- ],
- selectedAdditionalFees: [], // 宸查�夋嫨鐨勯檮鍔犺垂鐢�
+ qrCodeImage: '', // 浜岀淮鐮佸浘鐗囪矾寰�
+ paymentId: null,
+ pollTimer: null, // 杞瀹氭椂鍣�
+ additionalFeeTypes: [], // 浠庡瓧鍏稿姞杞�
+ additionalFeeList: [], // 宸叉坊鍔犵殑闄勫姞璐圭敤鍒楄〃
selectedFeeTypeName: '', // 閫変腑鐨勮垂鐢ㄧ被鍨嬪悕绉�
- newFeePrice: '' // 鏂板璐圭敤浠锋牸
+ selectedFeeTypeValue: '', // 閫変腑鐨勮垂鐢ㄧ被鍨嬪��
+ newFeePrice: '', // 鏂板璐圭敤浠锋牸
+ loading: false,
+ paySuccess: false, // 鏀粯鎴愬姛鏍囪
+ paidAmount: 0, // 宸叉敮浠橀噾棰�
+ paidPaymentList: [] // 宸叉敮浠樿褰曞垪琛�
}
},
computed: {
totalAmount() {
return this.baseAmount + this.additionalAmount;
},
+ // 鍓╀綑鏈敮浠橀噾棰�
+ remainingAmount() {
+ return this.totalAmount - this.paidAmount;
+ },
+ // 浠诲姟绫诲瀷鏄剧ず
+ taskTypeText() {
+ const typeMap = {
+ 'EMERGENCY_TRANSFER': '鎬ユ晳杞繍',
+ 'SCHEDULED_TRANSFER': '璁″垝杞繍',
+ 'EMERGENCY_RESCUE': '鎬ユ晳鎶㈡晳',
+ 'ORDINARY_TASK': '鏅�氫换鍔�'
+ }
+ return typeMap[this.task.type] || this.task.type
+ },
canSave() {
- // 鐜伴噾鍜屾寕甯愬彲浠ョ洿鎺ヤ繚瀛橈紝鍏朵粬鏀粯鏂瑰紡闇�瑕佹敮浠樻垚鍔熷悗鎵嶈兘淇濆瓨
+ // 宸插叏棰濇敮浠樺悗涓嶈兘鍐嶆淇濆瓨
+ if (this.remainingAmount <= 0) {
+ return false
+ }
+
if (!this.settlementAmount || !this.selectedPaymentMethod) {
- return false;
+ return false
}
- if (this.selectedPaymentMethod === '鐜伴噾' || this.selectedPaymentMethod === '鎸傚笎') {
- return true;
+ const amount = parseFloat(this.settlementAmount)
+
+ // 鏍¢獙缁撶畻閲戦蹇呴』澶т簬0涓斾笉瓒呰繃鍓╀綑閲戦
+ if (amount <= 0 || amount > this.remainingAmount) {
+ return false
}
- // 鏀粯瀹濄�佸井淇$瓑闇�瑕佹敮浠樻垚鍔�
- return this.paymentStatus === 'success';
+ const dictValue = this.getPaymentMethodValue(this.selectedPaymentMethod)
+
+ // 1-鐜伴噾, 6-鎸傝处, 7-鏄撳尰閫氭寕璐� 鍙互鐩存帴淇濆瓨
+ if (dictValue === '1' || dictValue === '6' || dictValue === '7') {
+ return true
+ }
+
+ // 3-寰俊鏀粯, 4-鏀粯瀹� 闇�瑕佹敮浠樻垚鍔熷悗鎵嶈兘淇濆瓨
+ if (dictValue === '3' || dictValue === '4') {
+ return this.paymentStatus === 'success'
+ }
+
+ // 2-閾惰杞处, 5-POS鏀舵 鐩存帴淇濆瓨
+ return true
},
// 鑾峰彇璐圭敤绫诲瀷鍚嶇О鏁扮粍
additionalFeeTypeNames() {
- return this.additionalFeeTypes.map(fee => fee.name);
+ return this.additionalFeeTypes.map(fee => fee.dictLabel);
}
},
onLoad(options) {
- // 瀹為檯椤圭洰涓繖閲屼細閫氳繃API鑾峰彇浠诲姟璇︽儏
- // const taskId = options.id;
- // this.getTaskDetail(taskId);
+ if (options.taskId) {
+ this.taskId = options.taskId
+ this.loadPaymentMethods() // 鍔犺浇鏀粯鏂瑰紡瀛楀吀
+ this.loadPaymentInfo()
+ this.loadFeeTypes()
+ } else {
+ this.$modal.showToast('浠诲姟ID涓嶈兘涓虹┖')
+ setTimeout(() => {
+ uni.navigateBack()
+ }, 1500)
+ }
+ },
+ onUnload() {
+ // 娓呴櫎杞瀹氭椂鍣�
+ if (this.pollTimer) {
+ clearInterval(this.pollTimer)
+ }
},
methods: {
- getTaskTypeText(type) {
- const typeMap = {
- 'maintenance': '缁翠慨淇濆吇',
- 'refuel': '鍔犳补',
- 'inspection': '宸℃',
- 'emergency': '杞繍浠诲姟',
- 'welfare': '绂忕杞�'
- }
- return typeMap[type] || '鏈煡绫诲瀷'
+ // 鍔犺浇鏀粯鏂瑰紡瀛楀吀
+ loadPaymentMethods() {
+ this.$dict.getDicts('task_payment_method').then(res => {
+ // 杩囨护鎺夐��娆�(8)鍜岀Н鍒�(9)锛屽彧鏄剧ず1-7
+ this.paymentMethodDict = (res.data || []).filter(item => {
+ const value = parseInt(item.dictValue)
+ return value >= 1 && value <= 7
+ })
+ // 鎻愬彇鏄剧ず鍚嶇О鏁扮粍鐢ㄤ簬picker
+ this.paymentMethods = this.paymentMethodDict.map(item => item.dictLabel)
+ }).catch(err => {
+ console.error('鍔犺浇鏀粯鏂瑰紡瀛楀吀澶辫触', err)
+ this.$modal.showToast('鍔犺浇鏀粯鏂瑰紡澶辫触')
+ })
+ },
+
+ // 鏍规嵁閫変腑鐨勫悕绉拌幏鍙栧瓧鍏稿��
+ getPaymentMethodValue(label) {
+ const method = this.paymentMethodDict.find(item => item.dictLabel === label)
+ return method ? method.dictValue : ''
+ },
+
+ // 鍔犺浇鏀粯淇℃伅
+ loadPaymentInfo() {
+ this.loading = true
+ getPaymentInfo(this.taskId).then(res => {
+ const data = res.data
+
+ // 浠诲姟鍩烘湰淇℃伅
+ this.task = {
+ taskNo: data.taskCode || '',
+ type: data.taskType || '',
+ vehicle: data.vehicleInfo || '鏈垎閰�',
+ startLocation: data.departureAddress || '鏈缃�',
+ endLocation: data.destinationAddress || '鏈缃�'
+ }
+
+ // 璐圭敤淇℃伅
+ this.baseAmount = data.transferPrice || 0
+ this.additionalAmount = data.additionalAmount || 0
+ this.additionalFeeList = data.additionalFees || []
+ this.paidAmount = data.paidAmount || 0
+ this.paidPaymentList = data.paidPayments || [] // 鍔犺浇宸叉敮浠樿褰曞垪琛�
+
+ // 榛樿缁撶畻閲戦涓哄墿浣欐湭鏀粯閲戦
+ const remaining = (data.totalAmount || 0) - this.paidAmount
+ this.settlementAmount = remaining > 0 ? remaining.toString() : ''
+
+ // 濡傛灉宸插叏棰濇敮浠橈紝鎻愮ず宸茬粨绠�
+ if (remaining <= 0) {
+ this.paySuccess = true
+ this.$modal.showToast('璇ヤ换鍔″凡缁撶畻')
+ }
+
+ this.loading = false
+ }).catch(err => {
+ console.error('鍔犺浇鏀粯淇℃伅澶辫触', err)
+ this.loading = false
+ this.$modal.showToast('鍔犺浇鏀粯淇℃伅澶辫触')
+ })
+ },
+
+ // 鍔犺浇璐圭敤绫诲瀷瀛楀吀
+ loadFeeTypes() {
+ this.$dict.getDicts('task_additional_fee_type').then(res => {
+ this.additionalFeeTypes = res.data || []
+ })
},
onPaymentMethodChange(e) {
- this.selectedPaymentMethod = this.paymentMethods[e.detail.value];
+ this.selectedPaymentMethod = this.paymentMethods[e.detail.value]
+ const dictValue = this.getPaymentMethodValue(this.selectedPaymentMethod)
- // 濡傛灉閫夋嫨鏀粯瀹濇垨寰俊锛屾樉绀轰簩缁寸爜
- if (this.selectedPaymentMethod === '鏀粯瀹�' || this.selectedPaymentMethod === '寰俊') {
- this.showQRCode = true;
- this.paymentStatus = 'pending';
- // 妯℃嫙鏀粯鐘舵�佹鏌�
- this.checkPaymentStatus();
+ // 3-寰俊鏀粯, 4-鏀粯瀹�
+ if (dictValue === '3' || dictValue === '4') {
+ this.openQRCodePopup(dictValue)
} else {
- this.showQRCode = false;
- this.paymentStatus = 'pending';
+ // 鍏朵粬鏀粯鏂瑰紡涓嶆樉绀轰簩缁寸爜
+ this.showQRCode = false
+ this.paymentStatus = 'pending'
}
+ },
+
+ // 鎵撳紑鏀粯浜岀淮鐮佸脊绐�
+ openQRCodePopup(dictValue) {
+ // 閲嶇疆鐘舵��
+ this.qrCodeImage = ''
+ this.paymentStatus = 'pending'
+ this.showQRCode = true
+
+ // 鎵撳紑寮圭獥
+ this.$refs.qrCodePopup.open()
+
+ // 鐢熸垚浜岀淮鐮�
+ this.generateQRCode(dictValue)
+ },
+
+ // 鍙栨秷鏀粯
+ cancelPayment() {
+ // 娓呴櫎杞
+ if (this.pollTimer) {
+ clearInterval(this.pollTimer);
+ this.pollTimer = null;
+ }
+
+ // 鍏抽棴寮圭獥
+ this.$refs.qrCodePopup.close();
+
+ // 閲嶇疆鐘舵��
+ this.selectedPaymentMethod = '';
+ this.showQRCode = false;
+ this.qrCodeImage = '';
+ this.paymentStatus = 'pending';
+ },
+
+ // 纭鏀粯鎴愬姛
+ confirmPaymentSuccess() {
+ // 鍏抽棴寮圭獥
+ this.$refs.qrCodePopup.close();
+
+ // 閲嶆柊鍔犺浇鏀粯淇℃伅锛屽埛鏂版樉绀�
+ this.loadPaymentInfo();
+
+ // 閲嶇疆鐘舵�侊紝鍑嗗涓嬩竴绗旀敮浠�
+ this.selectedPaymentMethod = '';
+ this.showQRCode = false;
+ this.qrCodeImage = '';
+ this.paymentStatus = 'pending';
+ this.remark = '';
+ // settlementAmount 浼氬湪 loadPaymentInfo 涓嚜鍔ㄨ缃负鍓╀綑閲戦
+ },
+
+ // 鐢熸垚鏀粯浜岀淮鐮�
+ generateQRCode(paymentMethod) {
+ // 鏍¢獙缁撶畻閲戦
+ if (!this.settlementAmount) {
+ this.$modal.showToast('璇峰厛杈撳叆缁撶畻閲戦');
+ this.$refs.qrCodePopup.close();
+ this.selectedPaymentMethod = '';
+ return;
+ }
+
+ const amount = parseFloat(this.settlementAmount);
+ if (amount <= 0) {
+ this.$modal.showToast('缁撶畻閲戦蹇呴』澶т簬0');
+ this.$refs.qrCodePopup.close();
+ this.selectedPaymentMethod = '';
+ return;
+ }
+
+ if (amount > this.remainingAmount) {
+ this.$modal.showToast(`缁撶畻閲戦涓嶈兘瓒呰繃鍓╀綑閲戦楼${this.remainingAmount}`);
+ this.$refs.qrCodePopup.close();
+ this.selectedPaymentMethod = '';
+ return;
+ }
+
+ const data = {
+ taskId: this.taskId,
+ paymentMethod: paymentMethod,
+ settlementAmount: amount,
+ remark: this.remark
+ };
+
+ createPayment(data).then(res => {
+ const result = res.data;
+ this.paymentId = result.paymentId;
+ this.qrCodeImage = result.codeUrl;
+ this.paymentStatus = 'pending';
+
+ // 寮�濮嬭疆璇㈡敮浠樼姸鎬�
+ this.checkPaymentStatus();
+ }).catch(err => {
+ console.error('鐢熸垚鏀粯浜岀淮鐮佸け璐�', err);
+ this.$modal.showToast('鐢熸垚鏀粯浜岀淮鐮佸け璐�');
+ this.$refs.qrCodePopup.close();
+ this.selectedPaymentMethod = '';
+ });
},
// 璐圭敤绫诲瀷閫夋嫨
onFeeTypeChange(e) {
- this.selectedFeeTypeName = this.additionalFeeTypeNames[e.detail.value];
+ const index = e.detail.value
+ this.selectedFeeTypeName = this.additionalFeeTypes[index].dictLabel
+ this.selectedFeeTypeValue = this.additionalFeeTypes[index].dictValue
},
+ // 杞妫�鏌ユ敮浠樼姸鎬�
checkPaymentStatus() {
- // 妯℃嫙鏀粯鐘舵�佹鏌ワ紝瀹為檯椤圭洰涓簲璇ラ�氳繃API杞妫�鏌ユ敮浠樼姸鎬�
- setTimeout(() => {
- // 妯℃嫙鏀粯鎴愬姛
- this.paymentStatus = 'success';
- }, 5000);
+ if (this.pollTimer) {
+ clearInterval(this.pollTimer);
+ }
+
+ this.pollTimer = setInterval(() => {
+ getPaymentStatus(this.taskId, this.paymentId).then(res => {
+ const data = res.data;
+ if (data.payStatus === 'PAID') {
+ // 鏀粯鎴愬姛
+ this.paymentStatus = 'success';
+ clearInterval(this.pollTimer);
+ this.pollTimer = null;
+
+ // 鍦ㄥ脊绐椾腑鏄剧ず鏀粯鎴愬姛鐘舵��
+ // 鐢ㄦ埛闇�瑕佺偣鍑�"纭畾"鎸夐挳鏉ュ叧闂脊绐楀苟鍒锋柊椤甸潰
+ }
+ }).catch(err => {
+ console.error('鏌ヨ鏀粯鐘舵�佸け璐�', err);
+ });
+ }, 2000); // 姣�2绉掕疆璇竴娆�
},
showAdditionalFees() {
@@ -293,76 +587,151 @@
return;
}
- // 鏌ユ壘閫変腑鐨勮垂鐢ㄧ被鍨嬩俊鎭�
- const selectedFeeType = this.additionalFeeTypes.find(fee => fee.name === this.selectedFeeTypeName);
-
- // 妫�鏌ユ槸鍚﹀凡瀛樺湪鍚屽悕璐圭敤
- const existingFee = this.selectedAdditionalFees.find(fee => fee.name === this.selectedFeeTypeName);
- if (existingFee) {
- existingFee.quantity += 1;
- } else {
- this.selectedAdditionalFees.push({
- name: this.selectedFeeTypeName,
- price: parseFloat(this.newFeePrice),
- quantity: 1
- });
+ // 璋冪敤鎺ュ彛娣诲姞闄勫姞璐圭敤
+ const data = {
+ taskId: this.taskId,
+ feeType: this.selectedFeeTypeValue,
+ feeName: this.selectedFeeTypeName,
+ unitAmount: parseFloat(this.newFeePrice),
+ quantity: 1,
+ remark: ''
}
- // 娓呯┖杈撳叆妗�
- this.selectedFeeTypeName = '';
- this.newFeePrice = '';
-
- this.calculateAdditionalAmount();
- this.$modal.showToast('璐圭敤娣诲姞鎴愬姛');
+ addAdditionalFee(data).then(res => {
+ this.$modal.showToast('璐圭敤娣诲姞鎴愬姛')
+
+ // 娓呯┖杈撳叆妗�
+ this.selectedFeeTypeName = ''
+ this.selectedFeeTypeValue = ''
+ this.newFeePrice = ''
+
+ // 閲嶆柊鍔犺浇鏀粯淇℃伅
+ this.loadPaymentInfo()
+ }).catch(err => {
+ console.error('娣诲姞璐圭敤澶辫触', err)
+ this.$modal.showToast('娣诲姞璐圭敤澶辫触')
+ })
},
- calculateAdditionalAmount() {
- this.additionalAmount = this.selectedAdditionalFees.reduce((total, fee) => {
- return total + (fee.price * fee.quantity);
- }, 0);
+ // 鍒犻櫎闄勫姞璐圭敤
+ removeFee(feeId) {
+ this.$modal.confirm('纭畾瑕佸垹闄よ璐圭敤鍚楋紵').then(() => {
+ removeAdditionalFee({ taskId: this.taskId, feeId }).then(res => {
+ this.$modal.showToast('鍒犻櫎鎴愬姛')
+ // 閲嶆柊鍔犺浇鏀粯淇℃伅
+ this.loadPaymentInfo()
+ }).catch(err => {
+ console.error('鍒犻櫎璐圭敤澶辫触', err)
+ this.$modal.showToast('鍒犻櫎璐圭敤澶辫触')
+ })
+ }).catch(() => {})
},
confirmAdditionalFees() {
- this.calculateAdditionalAmount();
this.closeAdditionalFeesPopup();
+ },
+
+ // 鑾峰彇鏀粯鏂瑰紡鏂囨湰
+ getPaymentMethodText(dictValue) {
+ const method = this.paymentMethodDict.find(item => item.dictValue === dictValue)
+ return method ? method.dictLabel : dictValue
+ },
+
+ // 鏍煎紡鍖栨敮浠樻椂闂�
+ formatPaymentTime(time) {
+ if (!time) return '鏈煡'
+ const date = new Date(time)
+ const year = date.getFullYear()
+ const month = String(date.getMonth() + 1).padStart(2, '0')
+ const day = String(date.getDate()).padStart(2, '0')
+ const hour = String(date.getHours()).padStart(2, '0')
+ const minute = String(date.getMinutes()).padStart(2, '0')
+ return `${year}-${month}-${day} ${hour}:${minute}`
},
saveSettlement() {
if (!this.settlementAmount) {
- this.$modal.showToast('璇疯緭鍏ョ粨绠楅噾棰�');
- return;
+ this.$modal.showToast('璇疯緭鍏ョ粨绠楅噾棰�')
+ return
}
if (!this.selectedPaymentMethod) {
- this.$modal.showToast('璇烽�夋嫨鏀粯鏂瑰紡');
- return;
+ this.$modal.showToast('璇烽�夋嫨鏀粯鏂瑰紡')
+ return
}
- // 妫�鏌ョ粨绠楅噾棰濇槸鍚﹀悎鐞�
- if (parseFloat(this.settlementAmount) > this.totalAmount) {
- this.$modal.showToast('缁撶畻閲戦涓嶈兘澶т簬鎬昏垂鐢�');
- return;
+ const amount = parseFloat(this.settlementAmount)
+
+ // 鏍¢獙缁撶畻閲戦蹇呴』澶т簬0
+ if (amount <= 0) {
+ this.$modal.showToast('缁撶畻閲戦蹇呴』澶т簬0')
+ return
}
- this.$modal.confirm('纭畾瑕佷繚瀛樼粨绠椾俊鎭悧锛�').then(() => {
- // 杩欓噷搴旇璋冪敤API淇濆瓨缁撶畻淇℃伅
- console.log('缁撶畻淇℃伅:', {
- taskId: this.task.id,
- baseAmount: this.baseAmount,
- additionalAmount: this.additionalAmount,
- totalAmount: this.totalAmount,
- settlementAmount: this.settlementAmount,
- paymentMethod: this.selectedPaymentMethod,
- remark: this.remark,
- additionalFees: this.selectedAdditionalFees
- });
-
- this.$modal.showToast('缁撶畻淇℃伅淇濆瓨鎴愬姛');
- // 杩斿洖浠诲姟鍒楄〃
- this.$tab.navigateBack();
- }).catch(() => {
- // 鍙栨秷鎿嶄綔
- });
+ // 鏍¢獙缁撶畻閲戦涓嶈兘瓒呰繃鍓╀綑閲戦
+ if (amount > this.remainingAmount) {
+ this.$modal.showToast(`缁撶畻閲戦涓嶈兘瓒呰繃鍓╀綑閲戦楼${this.remainingAmount}`)
+ return
+ }
+
+ const dictValue = this.getPaymentMethodValue(this.selectedPaymentMethod)
+
+ // 3-寰俊鏀粯, 4-鏀粯瀹� 宸茬粡鍦ㄩ�夋嫨鏃剁敓鎴愪簡浜岀淮鐮侊紝绛夊緟鏀粯瀹屾垚
+ if (dictValue === '3' || dictValue === '4') {
+ if (this.paymentStatus !== 'success') {
+ this.$modal.showToast('璇峰厛瀹屾垚鎵爜鏀粯')
+ return
+ }
+ }
+
+ // 1-鐜伴噾, 2-閾惰杞处, 5-POS鏀舵, 6-鎸傝处, 7-鏄撳尰閫氭寕璐� 闇�瑕佸垱寤烘敮浠樿褰�
+ if (dictValue === '1' || dictValue === '2' || dictValue === '5' || dictValue === '6' || dictValue === '7') {
+ this.$modal.confirm('纭畾瑕佷繚瀛樼粨绠椾俊鎭悧锛�').then(() => {
+ const data = {
+ taskId: this.taskId,
+ paymentMethod: dictValue,
+ settlementAmount: amount,
+ remark: this.remark
+ }
+
+ this.loading = true
+ createPayment(data).then(res => {
+ this.loading = false
+ const result = res.data
+ this.paymentId = result.paymentId
+ this.paymentStatus = 'success'
+
+ // 閲嶆柊鍔犺浇鏀粯淇℃伅浠ヨ幏鍙栨渶鏂扮殑宸叉敮浠橀噾棰�
+ this.loadPaymentInfo()
+
+ // 妫�鏌ユ槸鍚﹀叏棰濇敮浠橈紙浣跨敤鏈�鏂版暟鎹垽鏂級
+ setTimeout(() => {
+ if (this.remainingAmount <= 0) {
+ this.paySuccess = true
+ this.$modal.showToast('鏀粯鎴愬姛锛屽凡鍏ㄩ缁撶畻')
+ setTimeout(() => {
+ uni.navigateBack()
+ }, 1500)
+ } else {
+ this.$modal.showToast(`鏀粯鎴愬姛锛屽墿浣櫬�${this.remainingAmount.toFixed(2)}`)
+ // 閲嶇疆琛ㄥ崟锛屽噯澶囦笅涓�绗旀敮浠�
+ this.selectedPaymentMethod = ''
+ this.remark = ''
+ }
+ }, 300) // 绛夊緟鏁版嵁鍔犺浇瀹屾垚
+ }).catch(err => {
+ this.loading = false
+ console.error('鍒涘缓鏀粯澶辫触', err)
+ this.$modal.showToast('鍒涘缓鏀粯澶辫触')
+ })
+ }).catch(() => {
+ // 鍙栨秷鎿嶄綔
+ })
+ } else {
+ // 鎵爜鏀粯锛堝井淇�/鏀粯瀹濓級鐨勬儏鍐典笉搴旇璧板埌杩欓噷
+ // 鍥犱负鎵爜鏀粯鍦� confirmPaymentSuccess 涓鐞�
+ this.$modal.showToast('璇峰厛瀹屾垚鎵爜鏀粯')
+ }
}
}
}
@@ -505,6 +874,76 @@
}
}
+ .payment-history-section {
+ background-color: white;
+ border-radius: 15rpx;
+ padding: 30rpx;
+ margin-bottom: 20rpx;
+ box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+
+ .section-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ margin-bottom: 20rpx;
+ color: #333;
+ }
+
+ .payment-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20rpx 0;
+ border-bottom: 1rpx solid #f0f0f0;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ .payment-info {
+ flex: 1;
+
+ .payment-method {
+ margin-bottom: 10rpx;
+
+ .method-tag {
+ display: inline-block;
+ padding: 4rpx 12rpx;
+ border-radius: 6rpx;
+ font-size: 24rpx;
+ color: white;
+
+ &.method-cash {
+ background-color: #34C759;
+ }
+
+ &.method-on_account {
+ background-color: #ff9500;
+ }
+
+ &.method-wechat {
+ background-color: #09BB07;
+ }
+
+ &.method-alipay {
+ background-color: #1677FF;
+ }
+ }
+ }
+
+ .payment-time {
+ font-size: 24rpx;
+ color: #999;
+ }
+ }
+
+ .payment-amount {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #34C759;
+ }
+ }
+ }
+
.settlement-form {
background-color: white;
border-radius: 15rpx;
@@ -618,6 +1057,130 @@
}
}
+ // 鏀粯浜岀淮鐮佸脊绐楁牱寮�
+ .qr-popup-content {
+ width: 600rpx;
+ background-color: white;
+ border-radius: 20rpx;
+ overflow: hidden;
+
+ .qr-popup-header {
+ padding: 40rpx 30rpx 30rpx;
+ text-align: center;
+ border-bottom: 1rpx solid #f0f0f0;
+
+ .qr-popup-title {
+ font-size: 36rpx;
+ font-weight: bold;
+ color: #333;
+ }
+ }
+
+ .qr-popup-body {
+ padding: 40rpx 30rpx;
+ text-align: center;
+
+ .qr-code-box {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-bottom: 30rpx;
+
+ .qr-image {
+ width: 400rpx;
+ height: 400rpx;
+ border: 2rpx solid #f0f0f0;
+ border-radius: 10rpx;
+ padding: 20rpx;
+ box-sizing: border-box;
+ }
+ }
+
+ .qr-loading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 400rpx;
+ margin-bottom: 30rpx;
+
+ .loading-text {
+ margin-top: 20rpx;
+ font-size: 28rpx;
+ color: #999;
+ }
+ }
+
+ .qr-tip-text {
+ font-size: 28rpx;
+ color: #666;
+ margin-bottom: 20rpx;
+ }
+
+ .qr-amount {
+ font-size: 48rpx;
+ font-weight: bold;
+ color: #e54d42;
+ margin-bottom: 30rpx;
+ }
+
+ .payment-status-box {
+ padding: 20rpx;
+ border-radius: 10rpx;
+
+ .status-waiting {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10rpx;
+ font-size: 28rpx;
+ color: #ff9900;
+
+ text {
+ margin-left: 10rpx;
+ }
+ }
+
+ .status-paid {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10rpx;
+ font-size: 32rpx;
+ color: #4cd964;
+ font-weight: bold;
+
+ text {
+ margin-left: 10rpx;
+ }
+ }
+ }
+ }
+
+ .qr-popup-footer {
+ padding: 20rpx 30rpx 30rpx;
+
+ .cancel-pay-btn,
+ .confirm-pay-btn {
+ width: 100%;
+ height: 80rpx;
+ border-radius: 10rpx;
+ font-size: 32rpx;
+ border: none;
+ }
+
+ .cancel-pay-btn {
+ background-color: #f5f5f5;
+ color: #666;
+ }
+
+ .confirm-pay-btn {
+ background-color: #4cd964;
+ color: white;
+ }
+ }
+ }
+
.popup-content {
background-color: white;
border-top-left-radius: 20rpx;
@@ -713,6 +1276,7 @@
}
.fee-info {
+ flex: 1;
.fee-name {
font-size: 28rpx;
color: #333;
@@ -725,11 +1289,21 @@
}
}
+ .fee-actions {
+ display: flex;
+ align-items: center;
+ gap: 20rpx;
+ }
+
.fee-total {
font-size: 28rpx;
font-weight: bold;
color: #e54d42;
}
+
+ .delete-btn {
+ padding: 10rpx;
+ }
}
}
diff --git a/app/plugins/dict.js b/app/plugins/dict.js
new file mode 100644
index 0000000..62142a5
--- /dev/null
+++ b/app/plugins/dict.js
@@ -0,0 +1,7 @@
+import { getDicts } from '@/api/dict'
+
+export default {
+ getDicts(dictType) {
+ return getDicts(dictType)
+ }
+}
diff --git a/app/plugins/index.js b/app/plugins/index.js
index efbae15..1ce4e16 100644
--- a/app/plugins/index.js
+++ b/app/plugins/index.js
@@ -1,6 +1,7 @@
import tab from './tab'
import auth from './auth'
import modal from './modal'
+import dict from './dict'
export default {
install(Vue) {
@@ -10,5 +11,7 @@
Vue.prototype.$auth = auth
// 妯℃�佹瀵硅薄
Vue.prototype.$modal = modal
+ // 瀛楀吀瀵硅薄
+ Vue.prototype.$dict = dict
}
}
diff --git "a/doc/\346\224\257\344\273\230\345\212\237\350\203\275\345\256\236\347\216\260\350\277\233\345\272\246.md" "b/doc/\346\224\257\344\273\230\345\212\237\350\203\275\345\256\236\347\216\260\350\277\233\345\272\246.md"
new file mode 100644
index 0000000..381d31f
--- /dev/null
+++ "b/doc/\346\224\257\344\273\230\345\212\237\350\203\275\345\256\236\347\216\260\350\277\233\345\272\246.md"
@@ -0,0 +1,137 @@
+# 杞繍浠诲姟鏀粯鍔熻兘瀹炵幇杩涘害
+
+## 宸插畬鎴愮殑宸ヤ綔
+
+### 1. 鏁版嵁搴撹璁� 鉁�
+- 鉁� 鍒涘缓 `sys_task_additional_fee` 闄勫姞璐圭敤琛�
+- 鉁� 鍒涘缓 `sys_task_payment` 鏀粯璁板綍琛�
+- 鉁� 鍒涘缓闄勫姞璐圭敤绫诲瀷瀛楀吀 `task_additional_fee_type`
+- 鉁� SQL鑴氭湰锛歚sql/task_payment_tables.sql`
+
+### 2. 鍚庣瀹炰綋绫� 鉁�
+- 鉁� SysTaskAdditionalFee.java - 闄勫姞璐圭敤瀹炰綋
+- 鉁� SysTaskPayment.java - 鏀粯璁板綍瀹炰綋
+- 鉁� TaskPaymentInfoVO.java - 鏀粯淇℃伅VO
+- 鉁� TaskPaymentCreateVO.java - 鍒涘缓鏀粯VO
+- 鉁� TaskPaymentResultVO.java - 鏀粯缁撴灉VO
+
+### 3. 鍚庣Mapper灞� 鉁�
+- 鉁� SysTaskAdditionalFeeMapper.java / SysTaskAdditionalFeeMapper.xml
+- 鉁� SysTaskPaymentMapper.java / SysTaskPaymentMapper.xml
+
+### 4. 鍚庣Service灞� 鉁�
+- 鉁� ISysTaskPaymentService.java - 浠诲姟鏀粯Service鎺ュ彛
+- 鉁� SysTaskPaymentServiceImpl.java - 浠诲姟鏀粯Service瀹炵幇
+- 鉁� IPaymentModuleService.java - 鏀粯妯″潡璋冪敤鎺ュ彛
+- 鉁� PaymentModuleServiceImpl.java - 鏀粯妯″潡璋冪敤瀹炵幇锛堟ā鎷燂級
+
+### 5. 鍚庣Controller灞� 鉁�
+- 鉁� SysTaskPaymentController.java - 浠诲姟鏀粯鎺у埗鍣�
+ - GET /task/payment/info - 鑾峰彇浠诲姟鏀粯淇℃伅
+ - POST /task/payment/additional-fee/add - 鏂板闄勫姞璐圭敤
+ - POST /task/payment/additional-fee/remove - 鍒犻櫎闄勫姞璐圭敤
+ - POST /task/payment/create - 鍒涘缓鏀粯
+ - GET /task/payment/status - 鏌ヨ鏀粯鐘舵��
+- 鉁� PaymentCallbackController.java - 鏀粯鍥炶皟鎺у埗鍣�
+ - POST /payment/callback/wechat - 寰俊鏀粯鍥炶皟
+ - POST /payment/callback/alipay - 鏀粯瀹濇敮浠樺洖璋�
+
+### 6. 鍓嶇APP绔� 鉁�
+- 鉁� app/api/payment.js - 鏀粯API璋冪敤
+- 鉁� app/pages/task/settlement.vue - 缁撶畻椤甸潰鏀归��
+ - 鏀寔鍔犺浇鏀粯淇℃伅
+ - 鏀寔闄勫姞璐圭敤绠$悊锛堟柊澧�/鍒犻櫎锛�
+ - 鏀寔鍥涚鏀粯鏂瑰紡锛堢幇閲戙�佹寕甯愩�佸井淇°�佹敮浠樺疂锛�
+ - 寰俊/鏀粯瀹濇樉绀轰簩缁寸爜骞惰疆璇㈡敮浠樼姸鎬�
+ - 閲戦鏍¢獙锛堢粨绠楅噾棰濆繀椤荤瓑浜庢�婚噾棰濓級
+
+## 寰呭畬鎴愮殑宸ヤ綔
+
+### 7. 鍓嶇APP绔� - 浠诲姟鍒楄〃鍜岃鎯呭鍔犵粨绠楀叆鍙� 鉁�
+宸插畬鎴愮殑淇敼锛�
+- 鉁� app/pages/task/detail.vue - 浠诲姟璇︽儏椤靛鍔�"缁撶畻"鎸夐挳
+ - 浠呰浆杩愪换鍔℃樉绀虹粨绠楁寜閽�
+ - 瀹屾垚/鍙栨秷鐘舵�佷笉鏄剧ず缁撶畻鎸夐挳
+ - 鐐瑰嚮璺宠浆鑷崇粨绠楅〉闈�
+
+寰呭畬鎴愶細
+- [ ] app/pages/task/list.vue - 浠诲姟鍒楄〃鍗$墖澧炲姞"缁撶畻"鎸夐挳锛堝彲閫夛級
+
+### 8. 鍚庡彴绠$悊UI - 浠诲姟璇︽儏椤垫樉绀烘敮浠樹俊鎭� 鉁�
+宸插畬鎴愮殑淇敼锛�
+- 鉁� ruoyi-ui/src/api/task.js - 澧炲姞鏀粯鐩稿叧API鎺ュ彛
+ - getPaymentInfo - 鑾峰彇浠诲姟鏀粯淇℃伅
+ - getAdditionalFees - 鏌ヨ闄勫姞璐圭敤鍒楄〃
+ - getLatestPayment - 鏌ヨ鏈�鏂版敮浠樿褰�
+- 鉁� ruoyi-ui/src/views/task/detail/index.vue - 浠诲姟璇︽儏椤靛鍔犳敮浠樹俊鎭睍绀�
+ - 浠呰浆杩愪换鍔℃樉绀烘敮浠樹俊鎭崱鐗�
+ - 鏄剧ず鎴愪氦浠枫�侀檮鍔犺垂鐢ㄣ�佹�婚噾棰�
+ - 鏄剧ず鏀粯鐘舵�併�佹敮浠樻柟寮忋�佺粨绠楅噾棰�
+ - 鏄剧ず浜ゆ槗鍙枫�佹敮浠樻椂闂�
+ - 鏄剧ず闄勫姞璐圭敤鏄庣粏琛ㄦ牸锛堣垂鐢ㄥ悕绉般�佸崟浠枫�佹暟閲忋�佸皬璁°�佸娉級
+ - 鍙灞曠ず锛屼笉鏀寔缂栬緫
+
+### 9. 閰嶇疆鏂囦欢 鈴�
+闇�瑕佹坊鍔犵殑閰嶇疆锛�
+- [ ] application.yml - 澧炲姞鏀粯妯″潡閰嶇疆
+ ```yaml
+ payment:
+ module:
+ url: http://localhost:8081/pay # 鏀粯妯″潡鏈嶅姟鍦板潃
+ callback:
+ base-url: http://localhost:8080 # 鍥炶皟鍩虹URL
+ ```
+
+### 10. 闆嗘垚娴嬭瘯 鈴�
+- [ ] 娴嬭瘯闄勫姞璐圭敤鏂板/鍒犻櫎鍔熻兘
+- [ ] 娴嬭瘯鐜伴噾/鎸傚笎鏀粯娴佺▼
+- [ ] 娴嬭瘯寰俊/鏀粯瀹濅簩缁寸爜鐢熸垚
+- [ ] 娴嬭瘯鏀粯鐘舵�佽疆璇�
+- [ ] 娴嬭瘯鏀粯鍥炶皟澶勭悊
+- [ ] 娴嬭瘯閲戦鏍¢獙閫昏緫
+
+### 11. 鏀粯妯″潡瀵规帴 鈴�
+褰撳墠 PaymentModuleServiceImpl 鏄ā鎷熷疄鐜帮紝闇�瑕佹浛鎹负鐪熷疄鐨凥TTP璋冪敤锛�
+- [ ] 浣跨敤 RestTemplate 鎴� HttpClient 璋冪敤鏀粯妯″潡鎺ュ彛
+- [ ] 瀹炵幇绛惧悕楠岃瘉閫昏緫
+- [ ] 澶勭悊寮傚父鎯呭喌鍜岄噸璇曢�昏緫
+
+## 涓嬩竴姝ユ搷浣滃缓璁�
+
+馃帀 **鏍稿績鍔熻兘宸插叏閮ㄥ畬鎴愶紒**
+
+鎺ヤ笅鏉ラ渶瑕佸畬鎴愮殑宸ヤ綔锛�
+
+1. **浼樺厛绾ф渶楂橈細鏁版嵁搴撳垵濮嬪寲**
+ - 鎵ц `sql/task_payment_tables.sql` 鍒涘缓琛ㄥ拰瀛楀吀
+ - 纭闄勫姞璐圭敤绫诲瀷瀛楀吀姝g‘鍒涘缓
+
+2. **浼樺厛绾�2锛氶厤缃敮浠樻ā鍧�**
+ - 鍦� `application.yml` 涓厤缃敮浠樻ā鍧桿RL
+ - 閰嶇疆鍥炶皟鍩虹URL锛堝寘鎷煙鍚嶏級
+
+3. **浼樺厛绾�3锛氶泦鎴愭祴璇�**
+ - 娴嬭瘯闄勫姞璐圭敤绠$悊鍔熻兘
+ - 娴嬭瘯鐜伴噾/鎸傝处鏀粯娴佺▼
+ - 娴嬭瘯鏀粯淇℃伅灞曠ず锛圓PP绔拰鍚庡彴锛�
+ - 楠岃瘉閲戦璁$畻姝g‘鎬�
+
+4. **浼樺厛绾�4锛氬鎺ョ湡瀹炴敮浠樻ā鍧�**
+ - 鏍规嵁鏀粯妯″潡瀹為檯鎺ュ彛鏂囨。淇敼 `PaymentModuleServiceImpl.java`
+ - 鏇挎崲TODO鏍囪鐨勬ā鎷熶唬鐮佷负鐪熷疄鐨凥TTP璋冪敤
+ - 娴嬭瘯寰俊/鏀粯瀹濅簩缁寸爜鐢熸垚
+ - 娴嬭瘯鏀粯鐘舵�佽疆璇笌鍥炶皟
+ - 瀹炵幇绛惧悕楠岃瘉閫昏緫
+
+5. **鍙�夊姛鑳斤細浠诲姟鍒楄〃缁撶畻鎸夐挳**
+ - 鍦� `app/pages/task/list.vue` 浠诲姟鍗$墖澧炲姞鈥滅粨绠椻�濇寜閽�
+
+## 娉ㄦ剰浜嬮」
+
+1. **閲戦绮惧害**锛氭墍鏈夐噾棰濊绠椾娇鐢� BigDecimal锛屼繚鐣欎袱浣嶅皬鏁�
+2. **浜嬪姟绠$悊**锛氶檮鍔犺垂鐢ㄦ搷浣滃拰鏀粯鎿嶄綔宸叉坊鍔� @Transactional 娉ㄨВ
+3. **鏉冮檺鎺у埗**锛氭敮浠樼浉鍏虫帴鍙i渶瑕丣WT閴存潈锛岀‘淇濆彧鏈変换鍔$浉鍏充汉鍛樺彲鎿嶄綔
+4. **鏁版嵁鏉冮檺**锛氶伒寰幇鏈夐儴闂�/鍒嗗叕鍙搁殧绂昏鍒�
+5. **鍥炶皟瀹夊叏**锛氬疄闄呴」鐩腑闇�瑕佷弗鏍奸獙璇佸洖璋冪鍚�
+6. **浜岀淮鐮佽繃鏈�**锛氬墠绔簲鎻愮ず浜岀淮鐮佽繃鏈熸椂闂村苟鍏佽閲嶆柊鐢熸垚
+7. **鏀粯鐘舵�佷竴鑷存��**锛氫互鍚庣鏀粯鐘舵�佷负鍑嗭紝鍥炶皟涓庤疆璇繚鎸佷竴鑷�
diff --git "a/doc/\346\224\257\344\273\230\345\256\235\346\224\257\344\273\230\346\226\271\345\274\217\351\205\215\347\275\256\350\257\264\346\230\216.md" "b/doc/\346\224\257\344\273\230\345\256\235\346\224\257\344\273\230\346\226\271\345\274\217\351\205\215\347\275\256\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..ef7d589
--- /dev/null
+++ "b/doc/\346\224\257\344\273\230\345\256\235\346\224\257\344\273\230\346\226\271\345\274\217\351\205\215\347\275\256\350\257\264\346\230\216.md"
@@ -0,0 +1,85 @@
+# 鏀粯瀹濇敮浠樻柟寮忛厤缃鏄�
+
+## 姒傝堪
+
+鏈郴缁熸敮鎸佷袱绉嶆敮浠樺疂鏀粯鏂瑰紡锛�
+1. **瀹樻柟鏀粯瀹濆綋闈粯** - 浣跨敤鏀粯瀹濆畼鏂筍DK鐩存帴瀵规帴
+2. **绗笁鏂规敮浠樺疂鏀粯** - 閫氳繃鏃х郴缁熺殑绗笁鏂规帴鍙e鎺�
+
+## 閰嶇疆璇存槑
+
+### 1. 鏀粯鏂瑰紡閰嶇疆鍙傛暟
+
+鍦� `application.yml` 涓殑 `payment.alipay` 鑺傜偣涓嬫柊澧炰互涓嬮厤缃細
+
+```yaml
+# 鏀粯瀹濋厤缃�
+alipay:
+ # ... 鍏朵粬鍘熸湁閰嶇疆淇濇寔涓嶅彉 ...
+
+ # 鏀粯鏂瑰紡: OFFICIAL(瀹樻柟鏀粯瀹�) 鎴� THIRD_PARTY(绗笁鏂规敮浠樺疂)
+ paymentMethod: OFFICIAL
+
+ # 绗笁鏂规敮浠橀厤缃�
+ thirdParty:
+ enabled: true
+ url: https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php
+ defaultNotifyUrl: https://dsp.966120.com.cn/alipay/pay_notify
+ timeout: 30000 # 瓒呮椂鏃堕棿锛堟绉掞級
+```
+
+### 2. 閰嶇疆鍙傛暟璇﹁В
+
+| 鍙傛暟 | 榛樿鍊� | 璇存槑 |
+|------|--------|------|
+| paymentMethod | OFFICIAL | 鏀粯鏂瑰紡锛屽彲閫夊�硷細<br>- `OFFICIAL`: 瀹樻柟鏀粯瀹濆綋闈粯<br>- `THIRD_PARTY`: 绗笁鏂规敮浠樺疂鏀粯 |
+| thirdParty.enabled | true | 鏄惁鍚敤绗笁鏂规敮浠樺姛鑳� |
+| thirdParty.url | https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php | 绗笁鏂规敮浠樻帴鍙e湴鍧� |
+| thirdParty.defaultNotifyUrl | https://dsp.966120.com.cn/alipay/pay_notify | 榛樿鍥炶皟鍦板潃 |
+| thirdParty.timeout | 30000 | 璇锋眰瓒呮椂鏃堕棿锛堟绉掞級 |
+
+## 浣跨敤鏂瑰紡
+
+### 1. 浣跨敤瀹樻柟鏀粯瀹濆綋闈粯
+
+灏� `paymentMethod` 璁剧疆涓� `OFFICIAL`锛�
+
+```yaml
+payment:
+ alipay:
+ paymentMethod: OFFICIAL
+ # ... 鍏朵粬閰嶇疆
+```
+
+### 2. 浣跨敤绗笁鏂规敮浠樺疂鏀粯
+
+灏� `paymentMethod` 璁剧疆涓� `THIRD_PARTY`锛�
+
+```yaml
+payment:
+ alipay:
+ paymentMethod: THIRD_PARTY
+ # ... 鍏朵粬閰嶇疆
+```
+
+## 鍒囨崲娉ㄦ剰浜嬮」
+
+1. **鍒囨崲鏀粯鏂瑰紡鍚庨渶瑕侀噸鍚湇鍔�**鎵嶈兘鐢熸晥
+2. **宸插垱寤虹殑璁㈠崟涓嶅彈褰卞搷**锛屼粛浣跨敤鍒涘缓鏃剁殑鏀粯鏂瑰紡杩涜澶勭悊
+3. **寤鸿鍦ㄦ祴璇曠幆澧冧腑鍏呭垎娴嬭瘯**鍚庡啀鍒囨崲鍒扮敓浜х幆澧�
+
+## 鏁呴殰鎺掗櫎
+
+### 1. 鏀粯鏂瑰紡涓嶇敓鏁�
+
+妫�鏌ヤ互涓嬪嚑鐐癸細
+- 纭 `paymentMethod` 鍙傛暟鍊兼纭紙娉ㄦ剰澶у皬鍐欙級
+- 纭鏈嶅姟宸查噸鍚�
+- 鏌ョ湅鏃ュ織纭閰嶇疆鏄惁姝g‘鍔犺浇
+
+### 2. 绗笁鏂规敮浠樻帴鍙h皟鐢ㄥけ璐�
+
+妫�鏌ヤ互涓嬪嚑鐐癸細
+- 纭绗笁鏂规帴鍙e湴鍧�鏄惁鍙揪
+- 纭缃戠粶杩炴帴鏄惁姝e父
+- 妫�鏌ユ棩蹇椾腑鐨勯敊璇俊鎭�
\ No newline at end of file
diff --git "a/doc/\350\275\254\350\277\220\344\273\273\345\212\241\346\224\257\344\273\230\345\212\237\350\203\275\350\256\276\350\256\241\346\226\271\346\241\210.md" "b/doc/\350\275\254\350\277\220\344\273\273\345\212\241\346\224\257\344\273\230\345\212\237\350\203\275\350\256\276\350\256\241\346\226\271\346\241\210.md"
new file mode 100644
index 0000000..48c2b08
--- /dev/null
+++ "b/doc/\350\275\254\350\277\220\344\273\273\345\212\241\346\224\257\344\273\230\345\212\237\350\203\275\350\256\276\350\256\241\346\226\271\346\241\210.md"
@@ -0,0 +1,172 @@
+# 杞繍浠诲姟鏀粯鍔熻兘璁捐鏂规锛堟渶缁堢増锛�
+
+鏈柟妗堥拡瀵硅浆杩愪换鍔$殑"鎬婚涓�娆℃�ф敮浠�"鍦烘櫙锛氭�婚=鎴愪氦浠�+闄勫姞璐圭敤姹囨�伙紝鏀寔鐜伴噾銆佹寕甯愩�佸井淇°�佹敮浠樺疂鍥涚鏂瑰紡銆傚井淇�/鏀粯瀹濈敱"鐙珛鏀粯妯″潡"鐢熸垚浜岀淮鐮佷笌鍥炶皟锛屾垜鏂圭郴缁熶粎瀵规帴璇ユ敮浠樻ā鍧椼��
+
+## 涓�銆佹渶缁堢‘璁ょ偣
+
+- 缁撶畻閲戦蹇呴』涓庢�婚噾棰濅竴鑷达紙鎬婚噾棰�=鎴愪氦浠�+闄勫姞璐圭敤姹囨�伙級銆�
+- 鎸傚笎鏀粯浠呰褰曞娉ㄥ嵆鍙��
+- 闄勫姞璐圭敤绫诲瀷鐢卞悗鍙板瓧鍏哥淮鎶わ紙瀛楀吀绫诲瀷锛歵ask_additional_fee_type锛夈��
+- 寰俊銆佹敮浠樺疂鐢辩嫭绔嬫敮浠樻ā鍧楃敓鎴愭敮浠樹簩缁寸爜涓庢煡璇㈢粨鏋滐紝涓嶇洿鎺ラ泦鎴愬晢鎴峰彿閫昏緫銆�
+- 缁撶畻鍏ュ彛鍦� APP 鐨勪换鍔¤鎯呴〉鍜屼换鍔″垪琛ㄥ崱鐗囦笂閮借鏈夈��
+- 鍚庡彴 ruoyi-ui 鍙睍绀烘敮浠樹俊鎭笌闄勫姞璐圭敤鏄庣粏锛屼笉鍏佽淇敼銆�
+- 鍙睍绀�"鏈夋晥鏀粯"锛堟渶杩戜竴鏉″凡鏀粯鎴愬姛鐨勮褰曪級銆�
+- 浜ゆ槗鍗曞彿 outTradeNo 鏍煎紡锛歿taskCode}-{timestampMillis}锛宼imestamp 浣跨敤姣绾ф椂闂存埑銆�
+- 鍥炶皟璺緞 callbackUrl 鍦ㄥ垱寤轰簩缁寸爜鏃剁敱鎴戞柟浼犲叆锛屽繀椤讳负缁濆鍦板潃锛堝惈鍩熷悕涓庤矾寰勶級銆�
+- 閲戦鏍¢獙锛氭垜鏂瑰彂璧锋敮浠樺墠寮烘牎楠� settlementAmount==totalAmount锛涙敮浠樻ā鍧椾篃杩涜閲戦浜屾鏍¢獙銆�
+
+## 浜屻�佹�讳綋娴佺▼
+
+1. APP 鍦ㄤ换鍔″垪琛ㄤ笌浠诲姟璇︽儏澧炲姞"缁撶畻"鍏ュ彛锛岃烦杞粨绠楅〉銆�
+2. 缁撶畻椤垫媺鍙栨垚浜や环涓庨檮鍔犺垂鐢ㄥ垪琛紝鍏佽鏂板/鍒犻櫎闄勫姞璐圭敤锛屽疄鏃惰绠楁�婚噾棰濄��
+3. 閫夋嫨鏀粯鏂瑰紡骞跺彂璧锋敮浠橈細
+ - 鐜伴噾銆佹寕甯愶細鍚庣鐩存帴鏍囪宸叉敮浠樸��
+ - 寰俊銆佹敮浠樺疂锛氬悗绔皟鐢�"鏀粯妯″潡鍒涘缓浜岀淮鐮�"锛岃繑鍥� codeUrl 涓庤繃鏈熸椂闂寸粰鍓嶇灞曠ず锛涘墠绔疆璇㈡垜鏂�"鏀粯鐘舵�佹帴鍙�"銆�
+4. 鏀粯鎴愬姛鍚庯紝APP 鑷姩鎻愮ず骞跺睍绀�"鏀粯鎴愬姛"鐘舵�侊紱绂佺敤鍚庣画淇敼銆�
+5. ruoyi-ui 鍦ㄤ换鍔℃槑缁嗛〉鍙灞曠ず鏀粯淇℃伅涓庨檮鍔犺垂鐢ㄦ槑缁嗐��
+
+## 涓夈�佹暟鎹ā鍨�
+
+鏀粯涓庨檮鍔犺垂鐙珛寤鸿〃锛岄伩鍏嶆薄鏌撲换鍔′富琛細
+
+### sys_task_additional_fee锛堥檮鍔犺垂鐢ㄦ槑缁嗭級
+- 瀛楁锛歩d, task_id, fee_type(瀛楀吀), fee_name, unit_amount, quantity, total_amount, remark, created_by, created_time
+
+### sys_task_payment锛堜换鍔℃敮浠樿褰曪級
+- 瀛楁锛歩d, task_id, total_amount, settlement_amount(蹇呴』==total_amount), payment_method("CASH""ON_ACCOUNT""WECHAT""ALIPAY"), pay_status("UNPAID""PENDING""PAID""FAILED""REFUNDED"), pay_time, out_trade_no, trade_no, code_url, qr_expire_time, provider("WECHAT""ALIPAY"), payment_ref_id(鏀粯妯″潡杩斿洖鍞竴鏍囪瘑), callback_url, remark, created_by, created_time, update_time
+
+### 鎴愪氦浠锋潵婧�
+- SysTaskEmergency.transferPrice
+- 鎬婚噾棰�=transferPrice + sum(sys_task_additional_fee.total_amount)
+
+### 鏈夋晥鏀粯
+- 鍚屼竴浠诲姟灞曠ず鏈�杩戜竴鏉� pay_status="PAID"鐨勮褰曪紱鑻ヤ笉瀛樺湪鍒欐樉绀�"鏈敮浠�"
+
+## 鍥涖�佸瓧鍏镐笌閰嶇疆
+
+### 瀛楀吀绫诲瀷锛歵ask_additional_fee_type
+- 1锛氱瓑寰呰垂
+- 2锛氭媴鏋�
+- 3锛氬眳瀹禝CU
+- 4锛氬尰鐤楄澶�
+
+### 閰嶇疆椤�
+- payment.module.url锛氭敮浠樻ā鍧楁湇鍔″湴鍧�锛堥粯璁わ細http://localhost:8081/pay锛�
+- payment.callback.base-url锛氬洖璋冨熀纭�URL锛堥粯璁わ細http://localhost:8080锛�
+
+## 浜斻�佷笌鏀粯妯″潡鐨勫鎺ュ绾�
+
+### 鍒涘缓浜岀淮鐮侊紙鏀粯妯″潡锛�
+- 鎺ュ彛锛歅OST /pay/qrcode/create
+- 鍏ュ弬锛歰utTradeNo, amount, provider("WECHAT""ALIPAY"), subject, attach(濡� taskId/taskCode), callbackUrl锛堢粷瀵瑰湴鍧�锛�
+- 鍑哄弬锛歱aymentRefId, codeUrl, expireTime
+
+### 鏌ヨ鏀粯鐘舵�侊紙鏀粯妯″潡锛�
+- 鎺ュ彛锛欸ET /pay/status?paymentRefId=xxx 鎴� GET /pay/status?outTradeNo=xxx
+- 鍑哄弬锛歴tatus("PENDING""PAID""FAILED""CLOSED"), tradeNo, payTime
+
+### 鍥炶皟锛堟敮浠樻ā鍧� 鈫� 鎴戞柟锛�
+- 鎺ュ彛锛歅OST {callbackUrl}
+- 浣撳唴瀛楁锛歰utTradeNo, tradeNo, amount, status=PAID, sign 绛夛紱鎴戞柟楠岀鍚庢洿鏂颁负 PAID
+
+### 璁㈠崟鍙疯鑼�
+- outTradeNo锛歍ASK-{taskCode}-{timestampMillis}
+- subject锛�"杞繍浠诲姟缁撶畻锛坽taskCode}锛�"
+
+## 鍏�佹垜鏂瑰悗绔帴鍙o紙APP 鐢級
+
+### GET /task/payment/info
+- 鍏ュ弬锛歵askId
+- 鍑哄弬锛歵ransferPrice, additionalFees[], additionalAmount, totalAmount, latestPayment锛堝鏈夛級, paymentMethods["CASH""ON_ACCOUNT""WECHAT""ALIPAY"]
+
+### POST /task/additional-fee/add
+- 鍏ュ弬锛歵askId, feeType, feeName, unitAmount, quantity, remark
+- 鍑哄弬锛歛dditionalAmount, totalAmount
+
+### POST /task/additional-fee/remove
+- 鍏ュ弬锛歵askId, feeId
+- 鍑哄弬锛歛dditionalAmount, totalAmount
+
+### POST /task/payment/create
+- 鍏ュ弬锛歵askId, paymentMethod, settlementAmount(蹇呴』==totalAmount), remark
+- 琛屼负锛�
+ - CASH/ON_ACCOUNT锛氱洿鎺ュ垱寤烘敮浠樿褰曞苟缃负 PAID
+ - WECHAT/ALIPAY锛氱敓鎴� outTradeNo锛坱askCode-姣鏃堕棿鎴筹級銆佽绠� amount銆佸噯澶� subject/attach/callbackUrl锛涜皟鐢ㄦ敮浠樻ā鍧楀垱寤轰簩缁寸爜锛涗繚瀛� paymentRefId銆乧odeUrl銆乪xpireTime锛岀疆涓� PENDING 杩斿洖
+- 鍑哄弬锛歱aymentId, payStatus, codeUrl(浠呭井淇�/鏀粯瀹�), qrExpireTime
+
+### GET /task/payment/status
+- 鍏ュ弬锛歵askId 鎴� paymentId
+- 琛屼负锛氭牴鎹� paymentRefId 鎴� outTradeNo 璋冪敤鏀粯妯″潡鏌ヨ锛涜嫢杩斿洖 PAID锛屾垜鏂规洿鏂� sys_task_payment 涓� PAID 骞惰繑鍥炴渶鏂扮姸鎬�
+- 鍑哄弬锛歱ayStatus, tradeNo, payTime
+
+### POST /payment/callback/{provider}
+- 鏀粯妯″潡鍥炶皟鎺ュ彛锛岄獙绛惧悗鏇存柊鏀粯鐘舵�佷负 PAID
+
+## 涓冦�丄PP 鍓嶇鏀归��
+
+### 鍏ュ彛
+- 浠诲姟鍒楄〃鍗$墖涓庝换鍔¤鎯呴〉鍧囧鍔�"缁撶畻"鎸夐挳锛堣鎯呴〉閬靛惊鏃㈡湁鎸夐挳鏄剧ず瑙勫垯锛氫粎瀹屾垚/鍙栨秷鐘舵�侀殣钘忥級
+
+### 缁撶畻椤碉紙settlement.vue锛�
+- 鍒濆鍖栵細璋冪敤 /task/payment/info 鎷夊彇鎴愪氦浠枫�侀檮鍔犺垂鍒楄〃涓庢眹鎬�
+- 闄勫姞璐圭敤锛氭柊澧�/鍒犻櫎浣跨敤瀵瑰簲鎺ュ彛锛岃垂鐢ㄧ被鍨嬫潵鑷瓧鍏革紱鍒锋柊姹囨��
+- 鏀粯锛�
+ - 鐜伴噾/鎸傚笎锛氳皟鐢� /task/payment/create锛屾垚鍔熷悗鎻愮ず"鏀粯鎴愬姛"
+ - 寰俊/鏀粯瀹濓細璋冪敤 /task/payment/create 鑾峰彇 codeUrl 涓� qrExpireTime锛涘睍绀轰簩缁寸爜骞舵瘡 2~3 绉掕疆璇� /task/payment/status锛岀洿鍒� PAID 鎴栬秴鏃�
+- 鏍¢獙锛歴ettlementAmount 蹇呴』绛変簬 totalAmount锛涗簩缁寸爜杩囨湡鎻愮ず閲嶆柊鐢熸垚
+- 鎴愬姛鍚庯細绂佺敤闄勫姞璐圭敤涓庢敮浠樻搷浣滐紱鍙繑鍥炰换鍔¤鎯�
+
+## 鍏�乺uoyi-ui 鍚庡彴灞曠ず锛堝彧璇伙級
+
+### 浠诲姟鏄庣粏椤垫柊澧�"鏀粯淇℃伅"鍖哄潡
+- 灞曠ず锛氭敮浠樻柟寮忋�佺粨绠楅噾棰濄�佹�婚噾棰濄�佹敮浠樼姸鎬併�佷氦鏄撳彿銆佹敮浠樻椂闂�
+- 闄勫姞璐圭敤鏄庣粏鍒楄〃涓庢眹鎬�
+- 鑻ユ湁鍘嗗彶鏀粯璁板綍锛屼粎灞曠ず鏈�杩戜竴娆� PAID锛涗笉鎻愪緵缂栬緫鎴栭噸璇曞叆鍙�
+
+## 涔濄�佹潈闄愪笌瀹夊叏
+
+- APP 閴存潈锛欽WT锛涗粎浠诲姟褰掑睘鎴愬憳锛堝垱寤轰汉銆佹墽琛屼汉銆佽皟搴﹁鑹诧級鍙粨绠�
+- 鏁版嵁鏉冮檺锛氬欢缁幇鏈夐儴闂�/鍒嗗叕鍙搁殧绂昏鍒�
+- 鍥炶皟瀹夊叏锛歝allbackUrl 楠岀锛堢鍚嶇畻娉曠敱鏀粯妯″潡纭畾锛夛紝鍙姞婧� IP 鐧藉悕鍗�
+- 閲戦涓�鑷存�э細鍓嶅悗绔弻閲嶆牎楠岋紱浜岀淮鐮佸垱寤哄悗閲戦閿佸畾锛屽彉鏇撮檮鍔犺垂闇�閲嶆柊鍙戣捣鏀粯
+
+## 鍗併�佽竟鐣屽鐞�
+
+- 浜岀淮鐮佽繃鏈燂細鎻愮ず骞跺厑璁搁噸鏂板垱寤猴紱鏃ф敮浠樿褰曠疆涓� FAILED/CLOSED
+- 杞涓庡洖璋冪珵鎬侊細浠ユ垜鏂瑰悗绔敮浠樼姸鎬佷负鍑嗭紙鍥炶皟鎴栨煡璇㈡洿鏂帮級
+- 閫�娆�/鎾ら攢锛氭湰鏈熶笉瀹炵幇锛涘鍚庣画闇�瑕侊紝澧炲姞 REFUNDED 鐘舵�佷笌閫�娆炬帴鍙�
+
+## 鍗佷竴銆侀獙鏀舵爣鍑�
+
+- APP锛氶檮鍔犺垂鐢ㄧ淮鎶や笌鎬婚璁$畻姝g‘锛涘洓绉嶆敮浠樻祦绋嬮�氱晠锛涙壂鐮佹敮浠樻垚鍔熷悗鑷姩鎻愮ず涓庣鐢ㄤ慨鏀�
+- 鍚庡彴锛氬噯纭睍绀烘敮浠樹俊鎭笌闄勫姞璐圭敤鏄庣粏锛涘彧鏄剧ず鏈夋晥鏀粯
+- 鍚庣锛氭敮浠樹笌闄勫姞璐硅惤搴撴纭紱鍥炶皟涓庤疆璇㈠潎鑳藉噯纭洿鏂版敮浠樼姸鎬侊紱閲戦鏍¢獙涓ユ牸鐢熸晥
+
+## 鍗佷簩銆佸疄鐜版枃浠舵竻鍗�
+
+### 鏁版嵁搴�
+- sql/task_payment_tables.sql
+
+### 瀹炰綋绫�
+- SysTaskAdditionalFee.java
+- SysTaskPayment.java
+- TaskPaymentInfoVO.java
+- TaskPaymentCreateVO.java
+- TaskPaymentResultVO.java
+
+### Mapper
+- SysTaskAdditionalFeeMapper.java / SysTaskAdditionalFeeMapper.xml
+- SysTaskPaymentMapper.java / SysTaskPaymentMapper.xml
+
+### Service
+- ISysTaskPaymentService.java / SysTaskPaymentServiceImpl.java
+- IPaymentModuleService.java / PaymentModuleServiceImpl.java
+
+### Controller
+- SysTaskPaymentController.java
+- PaymentCallbackController.java
+
+### 鍓嶇锛堝緟瀹炵幇锛�
+- app/pages/task/settlement.vue锛堟敼閫狅級
+- app/api/payment.js锛堟柊澧烇級
+- ruoyi-ui/src/views/task/detail.vue锛堝鍔犳敮浠樹俊鎭睍绀猴級
diff --git a/dryad-payment/.gitignore b/dryad-payment/.gitignore
new file mode 100644
index 0000000..03075a4
--- /dev/null
+++ b/dryad-payment/.gitignore
@@ -0,0 +1,35 @@
+# Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+.mvn/wrapper/maven-wrapper.jar
+
+# IDE
+.idea/
+*.iws
+*.iml
+*.ipr
+.vscode/
+*.swp
+*.swo
+*~
+
+# Logs
+logs/
+*.log
+log/
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Application
+application-local.yml
+application-dev.yml
+application-prod.yml
diff --git "a/dryad-payment/API\346\216\245\345\217\243\346\226\207\346\241\243.md" "b/dryad-payment/API\346\216\245\345\217\243\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..50ba25e
--- /dev/null
+++ "b/dryad-payment/API\346\216\245\345\217\243\346\226\207\346\241\243.md"
@@ -0,0 +1,470 @@
+# 鏀粯妯″潡 API 鎺ュ彛鏂囨。
+
+## 鍩虹淇℃伅
+
+- **鏈嶅姟鍦板潃**: `http://your-domain:8080`
+- **鎺ュ彛鍗忚**: HTTP/HTTPS
+- **鏁版嵁鏍煎紡**: JSON
+- **瀛楃缂栫爜**: UTF-8
+
+---
+
+## 1. 鍙戣捣寰俊Native鏀粯
+
+### 鎺ュ彛璇存槑
+鐢熸垚寰俊鏀粯浜岀淮鐮侊紝鐢ㄦ埛鎵爜鍚庡畬鎴愭敮浠樸��
+
+### 璇锋眰淇℃伅
+- **璇锋眰鏂瑰紡**: `POST`
+- **璇锋眰璺緞**: `/api/pay/wechat/native`
+- **Content-Type**: `application/json`
+
+### 璇锋眰鍙傛暟
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 璇存槑 | 绀轰緥 |
+|--------|------|------|------|------|
+| bizOrderId | String | 鏄� | 涓氬姟璁㈠崟鍙凤紙鍞竴鏍囪瘑锛� | "ORDER20251123001" |
+| amount | Integer | 鏄� | 鏀粯閲戦锛堝崟浣嶏細鍒嗭級 | 10000 (琛ㄧず100鍏�) |
+| subject | String | 鏄� | 璁㈠崟鏍囬 | "鍟嗗搧璁㈠崟鏀粯" |
+| description | String | 鍚� | 璁㈠崟鎻忚堪 | "璐拱鍟嗗搧A脳1" |
+| callbackUrl | String | 鏄� | 涓氬姟鍥炶皟鍦板潃锛堟敮浠樻垚鍔熷悗閫氱煡锛� | "https://your-domain.com/notify" |
+
+### 璇锋眰绀轰緥
+
+```json
+{
+ "bizOrderId": "ORDER20251123001",
+ "amount": 10000,
+ "subject": "鍟嗗搧璁㈠崟鏀粯",
+ "description": "璐拱鍟嗗搧A脳1",
+ "callbackUrl": "https://your-domain.com/notify"
+}
+```
+
+### 鍝嶅簲鍙傛暟
+
+| 鍙傛暟鍚� | 绫诲瀷 | 璇存槑 |
+|--------|------|------|
+| code | Integer | 鍝嶅簲鐮侊紝200琛ㄧず鎴愬姛 |
+| msg | String | 鍝嶅簲娑堟伅 |
+| data | Object | 鍝嶅簲鏁版嵁 |
+| data.orderId | Long | 鏀粯璁㈠崟ID |
+| data.transactionId | Long | 浜ゆ槗娴佹按ID |
+| data.status | String | 璁㈠崟鐘舵�侊紙PENDING-寰呮敮浠橈級 |
+| data.qrBase64 | String | 浜岀淮鐮佸浘鐗嘊ase64缂栫爜 |
+| data.expireAt | String | 杩囨湡鏃堕棿锛�2灏忔椂锛� |
+
+### 鍝嶅簲绀轰緥
+
+```json
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛",
+ "data": {
+ "orderId": 1234567890,
+ "transactionId": 9876543210,
+ "status": "PENDING",
+ "qrBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
+ "expireAt": "2025-11-23 14:00:00"
+ }
+}
+```
+
+### 浣跨敤璇存槑
+
+1. 璋冪敤鎺ュ彛鑾峰彇 `qrBase64`
+2. 鍓嶇鐩存帴灏咮ase64鏄剧ず涓哄浘鐗囷細`<img src="{qrBase64}" />`
+3. 鐢ㄦ埛浣跨敤寰俊鎵爜鏀粯
+4. 鏀粯鎴愬姛鍚庯紝绯荤粺浼氳皟鐢ㄦ偍鎻愪緵鐨� `callbackUrl` 閫氱煡缁撴灉
+
+---
+
+## 2. 鍙戣捣鏀粯瀹濆綋闈粯
+
+### 鎺ュ彛璇存槑
+鐢熸垚鏀粯瀹濇敮浠樹簩缁寸爜锛岀敤鎴锋壂鐮佸悗瀹屾垚鏀粯銆�
+
+### 璇锋眰淇℃伅
+- **璇锋眰鏂瑰紡**: `POST`
+- **璇锋眰璺緞**: `/api/pay/alipay/precreate`
+- **Content-Type**: `application/json`
+
+### 璇锋眰鍙傛暟
+
+涓庡井淇℃敮浠樼浉鍚岋紝鍙傝�冧笂鏂硅〃鏍笺��
+
+### 璇锋眰绀轰緥
+
+```json
+{
+ "bizOrderId": "ORDER20251123002",
+ "amount": 10000,
+ "subject": "鍟嗗搧璁㈠崟鏀粯",
+ "description": "璐拱鍟嗗搧B脳1",
+ "callbackUrl": "https://your-domain.com/notify"
+}
+```
+
+### 鍝嶅簲绀轰緥
+
+```json
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛",
+ "data": {
+ "orderId": 1234567891,
+ "transactionId": 9876543211,
+ "status": "PENDING",
+ "qrBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
+ "expireAt": "2025-11-23 14:00:00"
+ }
+}
+```
+
+---
+
+## 3. 鏌ヨ璁㈠崟鐘舵��
+
+### 鎺ュ彛璇存槑
+鏌ヨ鏀粯璁㈠崟鐨勫綋鍓嶇姸鎬併��
+
+### 璇锋眰淇℃伅
+- **璇锋眰鏂瑰紡**: `GET`
+- **璇锋眰璺緞**: `/api/pay/orders/{orderId}`
+
+### 璺緞鍙傛暟
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|--------|------|------|------|
+| orderId | Long | 鏄� | 鏀粯璁㈠崟ID |
+
+### 璇锋眰绀轰緥
+
+```
+GET /api/pay/orders/1234567890
+```
+
+### 鍝嶅簲鍙傛暟
+
+| 鍙傛暟鍚� | 绫诲瀷 | 璇存槑 |
+|--------|------|------|
+| code | Integer | 鍝嶅簲鐮� |
+| msg | String | 鍝嶅簲娑堟伅 |
+| data | Object | 璁㈠崟璇︽儏 |
+| data.id | Long | 璁㈠崟ID |
+| data.bizOrderId | String | 涓氬姟璁㈠崟鍙� |
+| data.amount | Integer | 閲戦锛堝垎锛� |
+| data.currency | String | 甯佺锛圕NY锛� |
+| data.channel | String | 鏀粯娓犻亾锛圵ECHAT/ALIPAY锛� |
+| data.status | String | 璁㈠崟鐘舵�� |
+| data.subject | String | 璁㈠崟鏍囬 |
+| data.channelTradeNo | String | 娓犻亾浜ゆ槗鍙� |
+| data.paidAt | String | 鏀粯瀹屾垚鏃堕棿 |
+| data.expireAt | String | 杩囨湡鏃堕棿 |
+| data.createdAt | String | 鍒涘缓鏃堕棿 |
+
+### 璁㈠崟鐘舵�佽鏄�
+
+| 鐘舵�佺爜 | 璇存槑 |
+|--------|------|
+| INIT | 鍒濆鍖� |
+| PENDING | 寰呮敮浠� |
+| SUCCEEDED | 鏀粯鎴愬姛 |
+| FAILED | 鏀粯澶辫触 |
+| CANCELED | 宸插叧闂� |
+| EXPIRED | 宸茶繃鏈� |
+
+### 鍝嶅簲绀轰緥
+
+```json
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛",
+ "data": {
+ "id": 1234567890,
+ "bizOrderId": "ORDER20251123001",
+ "amount": 10000,
+ "currency": "CNY",
+ "channel": "WECHAT",
+ "status": "SUCCEEDED",
+ "subject": "鍟嗗搧璁㈠崟鏀粯",
+ "channelTradeNo": "4200001234567890",
+ "paidAt": "2025-11-23 12:30:00",
+ "expireAt": "2025-11-23 14:00:00",
+ "createdAt": "2025-11-23 12:00:00"
+ }
+}
+```
+
+---
+
+## 4. 鏌ヨ鏈�鏂颁氦鏄�
+
+### 鎺ュ彛璇存槑
+鏌ヨ璁㈠崟鐨勬渶鏂颁氦鏄撹褰曪紙鍖呭惈浜岀淮鐮侊級銆�
+
+### 璇锋眰淇℃伅
+- **璇锋眰鏂瑰紡**: `GET`
+- **璇锋眰璺緞**: `/api/pay/orders/{orderId}/transactions/latest`
+
+### 璇锋眰绀轰緥
+
+```
+GET /api/pay/orders/1234567890/transactions/latest
+```
+
+### 鍝嶅簲绀轰緥
+
+```json
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛",
+ "data": {
+ "id": 9876543210,
+ "orderId": 1234567890,
+ "channel": "WECHAT",
+ "clientType": "NATIVE",
+ "status": "PENDING",
+ "qrBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
+ "createdAt": "2025-11-23 12:00:00"
+ }
+}
+```
+
+---
+
+## 5. 涓氬姟鍥炶皟閫氱煡锛堥噸瑕侊級
+
+### 鎺ュ彛璇存槑
+褰撴敮浠樻垚鍔熷悗锛岀郴缁熶細涓诲姩璋冪敤鎮ㄥ湪鍙戣捣鏀粯鏃舵彁渚涚殑 `callbackUrl`锛岄�氱煡鏀粯缁撴灉銆�
+
+### 鍥炶皟鏂瑰紡
+- **璇锋眰鏂瑰紡**: `POST`
+- **Content-Type**: `application/json`
+- **璇锋眰鍦板潃**: 鎮ㄦ彁渚涚殑 `callbackUrl`
+
+### 璇锋眰澶�
+
+| 鍙傛暟鍚� | 璇存槑 |
+|--------|------|
+| Content-Type | application/json |
+| X-Signature | HMAC-SHA256绛惧悕锛岀敤浜庨獙璇佽姹傛潵婧� |
+| X-Nonce | 闅忔満瀛楃涓� |
+| X-Timestamp | 鏃堕棿鎴筹紙姣锛� |
+
+### 鍥炶皟鍙傛暟
+
+| 鍙傛暟鍚� | 绫诲瀷 | 璇存槑 |
+|--------|------|------|
+| tradeId | Long | 浜ゆ槗ID |
+| orderId | Long | 璁㈠崟ID |
+| bizOrderId | String | 涓氬姟璁㈠崟鍙� |
+| channel | String | 鏀粯娓犻亾锛圵ECHAT/ALIPAY锛� |
+| amount | Integer | 閲戦锛堝垎锛� |
+| currency | String | 甯佺锛圕NY锛� |
+| status | String | 璁㈠崟鐘舵�侊紙SUCCEEDED锛� |
+| channelTradeNo | String | 娓犻亾浜ゆ槗鍙� |
+| paidAt | String | 鏀粯瀹屾垚鏃堕棿 |
+| subject | String | 璁㈠崟鏍囬 |
+| description | String | 璁㈠崟鎻忚堪 |
+
+### 鍥炶皟绀轰緥
+
+```json
+{
+ "tradeId": 9876543210,
+ "orderId": 1234567890,
+ "bizOrderId": "ORDER20251123001",
+ "channel": "WECHAT",
+ "amount": 10000,
+ "currency": "CNY",
+ "status": "SUCCEEDED",
+ "channelTradeNo": "4200001234567890",
+ "paidAt": "2025-11-23 12:30:00",
+ "subject": "鍟嗗搧璁㈠崟鏀粯",
+ "description": "璐拱鍟嗗搧A脳1"
+}
+```
+
+### 绛惧悕楠岃瘉
+
+**绛惧悕绠楁硶**: HMAC-SHA256
+
+**绛惧悕鏁版嵁**: `payload + nonce + timestamp`
+
+**楠岃瘉姝ラ**:
+1. 鑾峰彇璇锋眰澶翠腑鐨� `X-Signature`銆乣X-Nonce`銆乣X-Timestamp`
+2. 鑾峰彇璇锋眰浣� JSON 瀛楃涓� `payload`
+3. 鎷兼帴瀛楃涓诧細`signData = payload + nonce + timestamp`
+4. 浣跨敤閰嶇疆鐨� `callbackSignSecret` 璁$畻 HMAC-SHA256
+5. 瀵规瘮璁$畻缁撴灉涓� `X-Signature` 鏄惁涓�鑷�
+
+**Java绀轰緥浠g爜**:
+```java
+String payload = "璇锋眰浣揓SON瀛楃涓�";
+String nonce = request.getHeader("X-Nonce");
+String timestamp = request.getHeader("X-Timestamp");
+String signature = request.getHeader("X-Signature");
+
+String signData = payload + nonce + timestamp;
+String calculated = SignUtil.hmacSha256(signData, callbackSignSecret);
+
+if (signature.equals(calculated)) {
+ // 绛惧悕楠岃瘉閫氳繃
+}
+```
+
+### 鍝嶅簲瑕佹眰
+
+鎮ㄧ殑鍥炶皟鎺ュ彛搴旇繑鍥烇細
+- **鎴愬姛**: HTTP 鐘舵�佺爜 200-299
+- **澶辫触**: 鍏朵粬鐘舵�佺爜锛堢郴缁熶細鑷姩閲嶈瘯锛�
+
+### 閲嶈瘯鏈哄埗
+
+濡傛灉鍥炶皟澶辫触锛岀郴缁熶細鎸変互涓嬮棿闅旇嚜鍔ㄩ噸璇曪紙鏈�澶�10娆★級锛�
+- 绔嬪嵆閲嶈瘯
+- 1鍒嗛挓鍚�
+- 5鍒嗛挓鍚�
+- 15鍒嗛挓鍚�
+- 60鍒嗛挓鍚�
+
+---
+
+## 6. 鍋ュ悍妫�鏌�
+
+### 鎺ュ彛璇存槑
+妫�鏌ユ湇鍔℃槸鍚︽甯歌繍琛屻��
+
+### 璇锋眰淇℃伅
+- **璇锋眰鏂瑰紡**: `GET`
+- **璇锋眰璺緞**: `/api/health`
+
+### 鍝嶅簲绀轰緥
+
+```json
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛",
+ "data": {
+ "status": "UP",
+ "service": "dryad-payment",
+ "time": "2025-11-23 12:00:00",
+ "message": "鏀粯妯″潡鏈嶅姟杩愯姝e父"
+ }
+}
+```
+
+---
+
+## 閿欒鐮佽鏄�
+
+| 閿欒鐮� | 璇存槑 |
+|--------|------|
+| 200 | 鎴愬姛 |
+| 500 | 鏈嶅姟鍣ㄥ唴閮ㄩ敊璇� |
+| 400 | 璇锋眰鍙傛暟閿欒 |
+| 404 | 璧勬簮涓嶅瓨鍦� |
+
+### 閿欒鍝嶅簲绀轰緥
+
+```json
+{
+ "code": 500,
+ "msg": "鏀粯鍒涘缓澶辫触: 寰俊涓嬪崟澶辫触",
+ "data": null
+}
+```
+
+---
+
+## 闆嗘垚绀轰緥
+
+### cURL绀轰緥
+
+**鍙戣捣寰俊鏀粯**:
+```bash
+curl -X POST http://localhost:8080/api/pay/wechat/native \
+ -H "Content-Type: application/json" \
+ -d '{
+ "bizOrderId": "ORDER20251123001",
+ "amount": 10000,
+ "subject": "鍟嗗搧璁㈠崟鏀粯",
+ "description": "璐拱鍟嗗搧A脳1",
+ "callbackUrl": "https://your-domain.com/notify"
+ }'
+```
+
+**鏌ヨ璁㈠崟**:
+```bash
+curl http://localhost:8080/api/pay/orders/1234567890
+```
+
+### JavaScript绀轰緥
+
+```javascript
+// 鍙戣捣鏀粯
+async function createPayment() {
+ const response = await fetch('http://localhost:8080/api/pay/wechat/native', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ bizOrderId: 'ORDER20251123001',
+ amount: 10000,
+ subject: '鍟嗗搧璁㈠崟鏀粯',
+ description: '璐拱鍟嗗搧A脳1',
+ callbackUrl: 'https://your-domain.com/notify'
+ })
+ });
+
+ const result = await response.json();
+ if (result.code === 200) {
+ // 鏄剧ず浜岀淮鐮�
+ document.getElementById('qrcode').src = result.data.qrBase64;
+ }
+}
+
+// 鏌ヨ璁㈠崟鐘舵��
+async function queryOrder(orderId) {
+ const response = await fetch(`http://localhost:8080/api/pay/orders/${orderId}`);
+ const result = await response.json();
+ return result.data.status;
+}
+```
+
+### Java绀轰緥
+
+```java
+// 鍙戣捣鏀粯
+RestTemplate restTemplate = new RestTemplate();
+String url = "http://localhost:8080/api/pay/wechat/native";
+
+Map<String, Object> request = new HashMap<>();
+request.put("bizOrderId", "ORDER20251123001");
+request.put("amount", 10000);
+request.put("subject", "鍟嗗搧璁㈠崟鏀粯");
+request.put("callbackUrl", "https://your-domain.com/notify");
+
+ResponseEntity<Map> response = restTemplate.postForEntity(url, request, Map.class);
+Map<String, Object> data = (Map<String, Object>) response.getBody().get("data");
+String qrBase64 = (String) data.get("qrBase64");
+```
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+1. **骞傜瓑鎬�**: 鍚屼竴 `bizOrderId` + `channel` 閲嶅璇锋眰浼氳繑鍥炲悓涓�璁㈠崟鍜屼簩缁寸爜
+2. **杩囨湡鏃堕棿**: 璁㈠崟鍒涘缓鍚�2灏忔椂鍐呮湁鏁堬紝瓒呮椂鑷姩杩囨湡
+3. **骞跺彂鎺у埗**: 鍚屼竴璁㈠崟鍚屾椂鍙厑璁镐竴绗旇繘琛屼腑鐨勪氦鏄�
+4. **鍥炶皟绛惧悕**: 鍔″繀楠岃瘉鍥炶皟绛惧悕锛岄槻姝吉閫犺姹�
+5. **閲戦鍗曚綅**: 鎵�鏈夐噾棰濆崟浣嶅潎涓�**鍒�**锛堜緥濡傦細100鍏� = 10000鍒嗭級
+6. **鐜閰嶇疆**: 鐢熶骇鐜璇蜂娇鐢℉TTPS鍗忚
+
+---
+
+## 鎶�鏈敮鎸�
+
+濡傛湁闂锛岃鑱旂郴鎶�鏈敮鎸佸洟闃熴��
diff --git a/dryad-payment/README.md b/dryad-payment/README.md
new file mode 100644
index 0000000..b6fbf6a
--- /dev/null
+++ b/dryad-payment/README.md
@@ -0,0 +1,254 @@
+# 鏀粯妯″潡椤圭洰璇存槑
+
+## 椤圭洰姒傝堪
+鍩轰簬DDD鏋舵瀯鐨勬敮浠樻ā鍧楋紝鏀寔寰俊鏀粯Native鍜屾敮浠樺疂褰撻潰浠樹簩缁寸爜鏀粯銆�
+
+## 鎶�鏈爤
+- Java 1.8
+- Spring Boot 2.5.15
+- MyBatis Plus 3.4.3.4
+- MySQL
+- ZXing (浜岀淮鐮佺敓鎴�)
+
+## 椤圭洰缁撴瀯
+
+```
+dryad-payment/
+鈹溾攢鈹� src/main/java/com/ruoyi/payment/
+鈹� 鈹溾攢鈹� PaymentApplication.java # 鍚姩绫�
+鈹� 鈹溾攢鈹� common/ # 閫氱敤绫�
+鈹� 鈹� 鈹斺攢鈹� AjaxResult.java # 缁熶竴鍝嶅簲缁撴灉
+鈹� 鈹溾攢鈹� domain/ # 棰嗗煙灞�
+鈹� 鈹� 鈹溾攢鈹� enums/ # 鏋氫妇
+鈹� 鈹� 鈹� 鈹溾攢鈹� PayChannel.java # 鏀粯娓犻亾
+鈹� 鈹� 鈹� 鈹溾攢鈹� OrderStatus.java # 璁㈠崟鐘舵��
+鈹� 鈹� 鈹� 鈹溾攢鈹� TransactionStatus.java # 浜ゆ槗鐘舵��
+鈹� 鈹� 鈹� 鈹斺攢鈹� ClientType.java # 瀹㈡埛绔被鍨�
+鈹� 鈹� 鈹斺攢鈹� model/ # 棰嗗煙妯″瀷
+鈹� 鈹� 鈹溾攢鈹� PaymentOrder.java # 鏀粯璁㈠崟(鑱氬悎鏍�)
+鈹� 鈹� 鈹溾攢鈹� PaymentTransaction.java # 鏀粯浜ゆ槗
+鈹� 鈹� 鈹溾攢鈹� NotifyLog.java # 娓犻亾鍥炶皟鏃ュ織
+鈹� 鈹� 鈹溾攢鈹� BizCallbackLog.java # 涓氬姟鍥炶皟鏃ュ織
+鈹� 鈹� 鈹斺攢鈹� OperationAudit.java # 鎿嶄綔瀹¤
+鈹� 鈹溾攢鈹� application/ # 搴旂敤灞�
+鈹� 鈹� 鈹斺攢鈹� service/
+鈹� 鈹� 鈹斺攢鈹� PaymentService.java # 鏀粯搴旂敤鏈嶅姟
+鈹� 鈹溾攢鈹� infrastructure/ # 鍩虹璁炬柦灞�
+鈹� 鈹� 鈹溾攢鈹� config/ # 閰嶇疆
+鈹� 鈹� 鈹� 鈹溾攢鈹� WechatPayConfig.java # 寰俊鏀粯閰嶇疆
+鈹� 鈹� 鈹� 鈹溾攢鈹� AlipayConfig.java # 鏀粯瀹濋厤缃�
+鈹� 鈹� 鈹� 鈹溾攢鈹� BusinessCallbackConfig.java # 涓氬姟鍥炶皟閰嶇疆
+鈹� 鈹� 鈹� 鈹斺攢鈹� QrCodeConfig.java # 浜岀淮鐮侀厤缃�
+鈹� 鈹� 鈹溾攢鈹� persistence/ # 鎸佷箙鍖�
+鈹� 鈹� 鈹� 鈹斺攢鈹� mapper/ # MyBatis Mapper
+鈹� 鈹� 鈹� 鈹溾攢鈹� PaymentOrderMapper.java
+鈹� 鈹� 鈹� 鈹溾攢鈹� PaymentTransactionMapper.java
+鈹� 鈹� 鈹� 鈹溾攢鈹� NotifyLogMapper.java
+鈹� 鈹� 鈹� 鈹溾攢鈹� BizCallbackLogMapper.java
+鈹� 鈹� 鈹� 鈹斺攢鈹� OperationAuditMapper.java
+鈹� 鈹� 鈹斺攢鈹� util/ # 宸ュ叿绫�
+鈹� 鈹� 鈹溾攢鈹� QrCodeUtil.java # 浜岀淮鐮佺敓鎴愬伐鍏�
+鈹� 鈹� 鈹斺攢鈹� SignUtil.java # 绛惧悕宸ュ叿
+鈹� 鈹斺攢鈹� interfaces/ # 鎺ュ彛灞�
+鈹� 鈹溾攢鈹� controller/ # 鎺у埗鍣�
+鈹� 鈹� 鈹溾攢鈹� PaymentController.java # 鏀粯鎺ュ彛
+鈹� 鈹� 鈹斺攢鈹� PaymentNotifyController.java# 鍥炶皟鎺ュ彛
+鈹� 鈹斺攢鈹� dto/ # 鏁版嵁浼犺緭瀵硅薄
+鈹� 鈹溾攢鈹� PaymentRequest.java # 鏀粯璇锋眰
+鈹� 鈹斺攢鈹� PaymentResponse.java # 鏀粯鍝嶅簲
+鈹溾攢鈹� src/main/resources/
+鈹� 鈹溾攢鈹� application.yml # 閰嶇疆鏂囦欢
+鈹� 鈹溾攢鈹� mapper/ # MyBatis XML
+鈹� 鈹� 鈹溾攢鈹� PaymentOrderMapper.xml
+鈹� 鈹� 鈹斺攢鈹� PaymentTransactionMapper.xml
+鈹� 鈹斺攢鈹� sql/
+鈹� 鈹斺攢鈹� schema.sql # 鏁版嵁搴撹〃缁撴瀯
+鈹溾攢鈹� doc/
+鈹� 鈹斺攢鈹� 璁捐鏂规.md # 璇︾粏璁捐鏂囨。
+鈹斺攢鈹� pom.xml # Maven閰嶇疆
+
+```
+
+## 宸插疄鐜板姛鑳�
+
+### 1. 鍩虹妗嗘灦 鉁�
+- DDD鍒嗗眰鏋舵瀯
+- Spring Boot椤圭洰閰嶇疆
+- MyBatis Plus闆嗘垚
+- 鏁版嵁搴撹〃缁撴瀯
+
+### 2. 棰嗗煙妯″瀷 鉁�
+- PaymentOrder (鏀粯璁㈠崟鑱氬悎鏍�)
+- PaymentTransaction (鏀粯浜ゆ槗)
+- NotifyLog (娓犻亾鍥炶皟鏃ュ織)
+- BizCallbackLog (涓氬姟鍥炶皟鏃ュ織)
+- OperationAudit (鎿嶄綔瀹¤)
+
+### 3. 鏍稿績鎺ュ彛 鉁�
+- POST /api/pay/wechat/native - 鍙戣捣寰俊Native鏀粯
+- POST /api/pay/alipay/precreate - 鍙戣捣鏀粯瀹濆綋闈粯
+- GET /api/pay/orders/{orderId} - 鏌ヨ璁㈠崟
+- GET /api/pay/orders/{orderId}/transactions/latest - 鏌ヨ鏈�鏂颁氦鏄�
+- POST /api/pay/notify/wechat - 寰俊鍥炶皟(妗嗘灦)
+- POST /api/pay/notify/alipay - 鏀粯瀹濆洖璋�(妗嗘灦)
+
+### 4. 宸ュ叿绫� 鉁�
+- 浜岀淮鐮佺敓鎴�(300脳300 PNG Base64)
+- MD5绛惧悕
+- HMAC-SHA256绛惧悕
+
+## 寰呭畬鎴愬姛鑳�
+
+### 1. 娓犻亾瀹㈡埛绔疄鐜� 鈿狅笍
+闇�瑕佸畬鍠勪互涓嬪唴瀹癸細
+- `WxPayV2Client`: 寰俊v2缁熶竴涓嬪崟銆佹煡璇€�佸叧闂�佺鍚嶉獙璇�
+- `AlipayF2FClient`: 鏀粯瀹濆綋闈粯涓嬪崟銆佹煡璇€�佸叧闂�佺鍚嶉獙璇�
+
+褰撳墠PaymentService涓殑 `callWechatUnifiedOrder()` 鍜� `callAlipayPrecreate()` 鏄ā鎷熷疄鐜般��
+
+### 2. 鍥炶皟澶勭悊瀹屽杽 鈿狅笍
+闇�瑕佸畬鍠� PaymentNotifyController 涓殑TODO閮ㄥ垎锛�
+- 寰俊XML鎶ユ枃瑙f瀽涓庣鍚嶉獙璇�
+- 鏀粯瀹濆弬鏁伴獙璇佷笌绛惧悕楠岃瘉
+- 璁㈠崟鍜屼氦鏄撶姸鎬佹洿鏂伴�昏緫
+- 瑙﹀彂涓氬姟鍥炶皟閫昏緫
+- 璁板綍NotifyLog瀹炵幇骞傜瓑
+
+### 3. 涓氬姟鍥炶皟鏈嶅姟 馃摑
+闇�瑕佸疄鐜帮細
+- `BizCallbackService`: 澶栭儴涓氬姟鍥炶皟瑙﹀彂
+- HMAC-SHA256绛惧悕鐢熸垚
+- HTTP POST璋冪敤
+- 閲嶈瘯鏈哄埗(0/1/5/15/60鍒嗛挓)
+- 璁板綍BizCallbackLog
+
+### 4. 姣忔棩瀵硅处浠诲姟 馃摑
+闇�瑕佸疄鐜帮細
+- `ReconciliationTask`: @Scheduled瀹氭椂浠诲姟
+- 璋冪敤娓犻亾鏌ヨ鎺ュ彛瀵规瘮鐘舵��
+- 鑷姩淇鐘舵�佸樊寮�
+- 鐢熸垚瀵硅处鎶ュ憡
+
+### 5. 璁㈠崟杩囨湡澶勭悊 馃摑
+闇�瑕佸疄鐜帮細
+- 瀹氭椂浠诲姟妫�鏌ヨ繃鏈熻鍗�
+- 灏嗚秴杩�2灏忔椂鏈敮浠樼殑璁㈠崟鏍囪涓篍XPIRED
+
+### 6. 绠$悊绔帴鍙� 馃摑
+闇�瑕佸疄鐜帮細
+- 璁㈠崟鍒楄〃鏌ヨ
+- 浜ゆ槗娴佹按鏌ヨ
+- 鍥炶皟鏃ュ織鏌ヨ
+- 鎵嬪伐閲嶅彂涓氬姟鍥炶皟
+- 瀵硅处浠诲姟鏌ヨ
+- 鎿嶄綔瀹¤璁板綍
+
+## 蹇�熷紑濮�
+
+### 1. 鐜鍑嗗
+- JDK 1.8
+- MySQL 5.7+
+- Maven 3.6+
+
+### 2. 鏁版嵁搴撳垵濮嬪寲
+```bash
+# 鎵цSQL鑴氭湰鍒涘缓鏁版嵁搴撳拰琛�
+mysql -u root -p < src/main/resources/sql/schema.sql
+```
+
+### 3. 閰嶇疆淇敼
+淇敼 `src/main/resources/application.yml`:
+```yaml
+spring:
+ datasource:
+ url: jdbc:mysql://localhost:3306/dryad_payment?...
+ username: your_username
+ password: your_password
+
+payment:
+ wechat:
+ appId: your_wechat_appid
+ mchId: your_wechat_mchid
+ mchKey: your_wechat_mchkey
+
+ alipay:
+ appId: your_alipay_appid
+ privateKey: your_alipay_private_key
+ alipayPublicKey: alipay_public_key
+```
+
+### 4. 缂栬瘧杩愯
+```bash
+# 缂栬瘧
+mvn clean package
+
+# 杩愯
+java -jar target/dryad-payment-1.0.0.jar
+
+# 鎴栫洿鎺ヨ繍琛�
+mvn spring-boot:run
+```
+
+### 5. 娴嬭瘯鎺ュ彛
+```bash
+# 鍙戣捣寰俊Native鏀粯
+curl -X POST http://localhost:8080/api/pay/wechat/native \
+ -H "Content-Type: application/json" \
+ -d '{
+ "bizOrderId": "TEST20251123001",
+ "amount": 100,
+ "subject": "娴嬭瘯璁㈠崟",
+ "description": "杩欐槸涓�涓祴璇曡鍗�",
+ "callbackUrl": "http://your-domain.com/callback"
+ }'
+
+# 鏌ヨ璁㈠崟
+curl http://localhost:8080/api/pay/orders/1
+```
+
+## 鏍稿績涓氬姟閫昏緫
+
+### 鍚屼竴璁㈠崟鍙厑璁镐竴绗旇繘琛屼腑鐨勪氦鏄�
+鍦� `PaymentService` 涓凡瀹炵幇锛�
+```java
+// 妫�鏌ユ槸鍚﹀瓨鍦ㄥ緟鏀粯浜ゆ槗
+PaymentTransaction existingTransaction =
+ paymentTransactionMapper.selectPendingByOrderId(order.getId());
+if (existingTransaction != null) {
+ // 鐩存帴杩斿洖宸叉湁浜ゆ槗
+ return buildPaymentResponse(order, existingTransaction);
+}
+```
+
+### 璁㈠崟2灏忔椂杩囨湡
+鍒涘缓璁㈠崟鏃惰缃細
+```java
+order.setExpireAt(LocalDateTime.now().plusHours(2));
+```
+
+### 浜岀淮鐮丅ase64杩斿洖
+浣跨敤ZXing鐢熸垚300脳300 PNG浜岀淮鐮佸苟杞崲涓築ase64銆�
+
+## 鍚庣画寮�鍙戝缓璁�
+
+1. **浼樺厛瀹屾垚娓犻亾瀹㈡埛绔�**: 杩欐槸鏍稿績鍔熻兘锛屽缓璁弬鑰冨井淇℃敮浠樺拰鏀粯瀹濆畼鏂筍DK鏂囨。瀹炵幇
+2. **瀹屽杽鍥炶皟澶勭悊**: 瀹炵幇绛惧悕楠岃瘉鍜岀姸鎬佹洿鏂帮紝纭繚鏁版嵁涓�鑷存��
+3. **瀹炵幇涓氬姟鍥炶皟**: 纭繚澶栭儴绯荤粺鑳藉強鏃舵敹鍒版敮浠樼粨鏋�
+4. **娣诲姞鍗曞厓娴嬭瘯**: 瀵规牳蹇冧笟鍔¢�昏緫缂栧啓娴嬭瘯鐢ㄤ緥
+5. **鐩戞帶涓庡憡璀�**: 娣诲姞鏃ュ織鐩戞帶鍜屽紓甯稿憡璀�
+
+## 娉ㄦ剰浜嬮」
+
+1. 閰嶇疆鏂囦欢涓殑瀵嗛挜闇�瑕佸Ε鍠勪繚绠★紝涓嶈鎻愪氦鍒颁唬鐮佷粨搴�
+2. 鐢熶骇鐜闇�瑕侀厤缃瓾TTPS
+3. 鍥炶皟URL闇�瑕佽兘鍏綉璁块棶
+4. 寤鸿浣跨敤Redis缂撳瓨璁㈠崟鍜屼氦鏄撴暟鎹�
+5. 鐢熶骇鐜闇�瑕侀厤缃垎甯冨紡閿侀槻姝㈠苟鍙戝垱寤轰氦鏄�
+
+## 鍙傝�冩枃妗�
+- 璇︾粏璁捐鏂规: `doc/璁捐鏂规.md`
+- 寰俊鏀粯v2鏂囨。: https://pay.weixin.qq.com/wiki/doc/api/native.php
+- 鏀粯瀹濆綋闈粯鏂囨。: https://opendocs.alipay.com/open/194/105072
+
+## 璁稿彲璇�
+鏈」鐩粎渚涘涔犱娇鐢ㄣ��
diff --git "a/dryad-payment/doc/\344\273\243\347\240\201\347\224\237\346\210\220\346\200\273\347\273\223.md" "b/dryad-payment/doc/\344\273\243\347\240\201\347\224\237\346\210\220\346\200\273\347\273\223.md"
new file mode 100644
index 0000000..457db19
--- /dev/null
+++ "b/dryad-payment/doc/\344\273\243\347\240\201\347\224\237\346\210\220\346\200\273\347\273\223.md"
@@ -0,0 +1,268 @@
+# 鏀粯妯″潡浠g爜鐢熸垚瀹屾垚鎬荤粨
+
+## 鉁� 宸插畬鎴愬唴瀹�
+
+### 1. 椤圭洰鍩虹鏋舵瀯
+- 鉁� Maven椤圭洰閰嶇疆 (pom.xml)
+- 鉁� Spring Boot 2.5.15 鍚姩绫�
+- 鉁� application.yml 閰嶇疆鏂囦欢
+- 鉁� 鏁版嵁搴撹〃缁撴瀯 SQL (7寮犺〃)
+- 鉁� MyBatis Plus 闆嗘垚閰嶇疆
+- 鉁� .gitignore 閰嶇疆
+
+### 2. DDD鍒嗗眰鏋舵瀯
+
+#### Domain灞傦紙棰嗗煙灞傦級
+- 鉁� 鏋氫妇绫�
+ - PayChannel (鏀粯娓犻亾)
+ - OrderStatus (璁㈠崟鐘舵��)
+ - TransactionStatus (浜ゆ槗鐘舵��)
+ - ClientType (瀹㈡埛绔被鍨�)
+- 鉁� 棰嗗煙妯″瀷
+ - PaymentOrder (鏀粯璁㈠崟 - 鑱氬悎鏍�)
+ - PaymentTransaction (鏀粯浜ゆ槗)
+ - NotifyLog (娓犻亾鍥炶皟鏃ュ織)
+ - BizCallbackLog (涓氬姟鍥炶皟鏃ュ織)
+ - OperationAudit (鎿嶄綔瀹¤)
+
+#### Infrastructure灞傦紙鍩虹璁炬柦灞傦級
+- 鉁� 閰嶇疆绫�
+ - WechatPayConfig (寰俊鏀粯閰嶇疆)
+ - AlipayConfig (鏀粯瀹濋厤缃�)
+ - BusinessCallbackConfig (涓氬姟鍥炶皟閰嶇疆)
+ - QrCodeConfig (浜岀淮鐮侀厤缃�)
+- 鉁� Mapper鎺ュ彛
+ - PaymentOrderMapper + XML
+ - PaymentTransactionMapper + XML
+ - NotifyLogMapper
+ - BizCallbackLogMapper
+ - OperationAuditMapper
+- 鉁� 宸ュ叿绫�
+ - QrCodeUtil (浜岀淮鐮佺敓鎴� - 300脳300 PNG Base64)
+ - SignUtil (MD5 + HMAC-SHA256绛惧悕)
+
+#### Application灞傦紙搴旂敤灞傦級
+- 鉁� PaymentService
+ - 鍒涘缓寰俊Native鏀粯
+ - 鍒涘缓鏀粯瀹濆綋闈粯
+ - 鍚屼竴璁㈠崟鍙厑璁镐竴绗旇繘琛屼腑浜ゆ槗鐨勯�昏緫
+ - 璁㈠崟2灏忔椂杩囨湡鏃堕棿璁剧疆
+ - 鏌ヨ璁㈠崟鍜屼氦鏄�
+
+#### Interfaces灞傦紙鎺ュ彛灞傦級
+- 鉁� Controller
+ - PaymentController (鏀粯鎺ュ彛)
+ - POST /api/pay/wechat/native
+ - POST /api/pay/alipay/precreate
+ - GET /api/pay/orders/{orderId}
+ - GET /api/pay/orders/{orderId}/transactions/latest
+ - PaymentNotifyController (鍥炶皟鎺ュ彛妗嗘灦)
+ - POST /api/pay/notify/wechat
+ - POST /api/pay/notify/alipay
+ - HealthController (鍋ュ悍妫�鏌�)
+ - GET /api/health
+- 鉁� DTO
+ - PaymentRequest (鏀粯璇锋眰)
+ - PaymentResponse (鏀粯鍝嶅簲)
+
+#### Common灞傦紙閫氱敤灞傦級
+- 鉁� AjaxResult (缁熶竴鍝嶅簲缁撴灉)
+
+### 3. 鏂囨。
+- 鉁� doc/璁捐鏂规.md (瀹屾暣鐨凞DD璁捐鏂囨。)
+- 鉁� README.md (椤圭洰璇存槑鍜屼娇鐢ㄦ寚鍗�)
+
+## 馃搵 椤圭洰鐗规��
+
+### 宸插疄鐜扮殑鏍稿績鐗规��
+1. 鉁� **鍚屼竴璁㈠崟浠呬竴绗旇繘琛屼腑浜ゆ槗**: 鍦≒aymentService涓疄鐜帮紝妫�鏌ENDING鐘舵�佷氦鏄�
+2. 鉁� **璁㈠崟2灏忔椂鑷姩杩囨湡**: 鍒涘缓璁㈠崟鏃惰缃甧xpireAt
+3. 鉁� **浜岀淮鐮丅ase64杩斿洖**: 浣跨敤ZXing鐢熸垚300脳300 PNG骞惰浆Base64
+4. 鉁� **閰嶇疆缁熶竴绠$悊**: application.yml闆嗕腑閰嶇疆
+5. 鉁� **涔愯閿佸苟鍙戞帶鍒�**: PaymentOrder浣跨敤@Version
+6. 鉁� **鍙傛暟鏍¢獙**: 浣跨敤@Validated娉ㄨВ
+7. 鉁� **鏃ュ織璁板綍**: 浣跨敤@Slf4j
+
+### 寰呭疄鐜扮殑鍔熻兘锛堝凡棰勭暀TODO锛�
+1. 鈿狅笍 寰俊鏀粯v2瀹㈡埛绔� (WxPayV2Client)
+ - 缁熶竴涓嬪崟鎺ュ彛瀹炵幇
+ - MD5绛惧悕鐢熸垚涓庨獙璇�
+ - XML鎶ユ枃瑙f瀽
+
+2. 鈿狅笍 鏀粯瀹濆鎴风 (AlipayF2FClient)
+ - 褰撻潰浠樹笅鍗曟帴鍙e疄鐜�
+ - RSA2绛惧悕楠岃瘉
+
+3. 鈿狅笍 娓犻亾鍥炶皟瀹屾暣澶勭悊
+ - 寰俊XML鎶ユ枃瑙f瀽
+ - 鏀粯瀹濆弬鏁伴獙璇�
+ - 璁㈠崟鐘舵�佹洿鏂�
+ - 涓氬姟鍥炶皟瑙﹀彂
+ - NotifyLog璁板綍瀹炵幇骞傜瓑
+
+4. 馃摑 涓氬姟鍥炶皟鏈嶅姟 (BizCallbackService)
+ - HMAC-SHA256绛惧悕
+ - HTTP POST璋冪敤
+ - 閲嶈瘯鏈哄埗
+
+5. 馃摑 姣忔棩瀵硅处浠诲姟 (ReconciliationTask)
+ - @Scheduled瀹氭椂浠诲姟
+ - 娓犻亾鐘舵�佹煡璇�
+ - 鑷姩淇宸紓
+
+6. 馃摑 璁㈠崟杩囨湡澶勭悊
+ - 瀹氭椂浠诲姟鏍囪EXPIRED
+
+7. 馃摑 绠$悊绔帴鍙�
+ - 璁㈠崟鍒楄〃/璇︽儏
+ - 浜ゆ槗娴佹按鏌ヨ
+ - 鍥炶皟鏃ュ織鏌ヨ
+ - 鎵嬪伐閲嶅彂鍥炶皟
+ - 瀵硅处缁撴灉鏌ヨ
+
+## 馃搨 椤圭洰鏂囦欢娓呭崟
+
+```
+dryad-payment/
+鈹溾攢鈹� pom.xml 鉁�
+鈹溾攢鈹� README.md 鉁�
+鈹溾攢鈹� .gitignore 鉁�
+鈹溾攢鈹� doc/
+鈹� 鈹斺攢鈹� 璁捐鏂规.md 鉁�
+鈹溾攢鈹� src/main/java/com/ruoyi/payment/
+鈹� 鈹溾攢鈹� PaymentApplication.java 鉁�
+鈹� 鈹溾攢鈹� common/
+鈹� 鈹� 鈹斺攢鈹� AjaxResult.java 鉁�
+鈹� 鈹溾攢鈹� domain/
+鈹� 鈹� 鈹溾攢鈹� enums/
+鈹� 鈹� 鈹� 鈹溾攢鈹� PayChannel.java 鉁�
+鈹� 鈹� 鈹� 鈹溾攢鈹� OrderStatus.java 鉁�
+鈹� 鈹� 鈹� 鈹溾攢鈹� TransactionStatus.java 鉁�
+鈹� 鈹� 鈹� 鈹斺攢鈹� ClientType.java 鉁�
+鈹� 鈹� 鈹斺攢鈹� model/
+鈹� 鈹� 鈹溾攢鈹� PaymentOrder.java 鉁�
+鈹� 鈹� 鈹溾攢鈹� PaymentTransaction.java 鉁�
+鈹� 鈹� 鈹溾攢鈹� NotifyLog.java 鉁�
+鈹� 鈹� 鈹溾攢鈹� BizCallbackLog.java 鉁�
+鈹� 鈹� 鈹斺攢鈹� OperationAudit.java 鉁�
+鈹� 鈹溾攢鈹� application/service/
+鈹� 鈹� 鈹斺攢鈹� PaymentService.java 鉁�
+鈹� 鈹溾攢鈹� infrastructure/
+鈹� 鈹� 鈹溾攢鈹� config/
+鈹� 鈹� 鈹� 鈹溾攢鈹� WechatPayConfig.java 鉁�
+鈹� 鈹� 鈹� 鈹溾攢鈹� AlipayConfig.java 鉁�
+鈹� 鈹� 鈹� 鈹溾攢鈹� BusinessCallbackConfig.java 鉁�
+鈹� 鈹� 鈹� 鈹斺攢鈹� QrCodeConfig.java 鉁�
+鈹� 鈹� 鈹溾攢鈹� persistence/mapper/
+鈹� 鈹� 鈹� 鈹溾攢鈹� PaymentOrderMapper.java 鉁�
+鈹� 鈹� 鈹� 鈹溾攢鈹� PaymentTransactionMapper.java 鉁�
+鈹� 鈹� 鈹� 鈹溾攢鈹� NotifyLogMapper.java 鉁�
+鈹� 鈹� 鈹� 鈹溾攢鈹� BizCallbackLogMapper.java 鉁�
+鈹� 鈹� 鈹� 鈹斺攢鈹� OperationAuditMapper.java 鉁�
+鈹� 鈹� 鈹斺攢鈹� util/
+鈹� 鈹� 鈹溾攢鈹� QrCodeUtil.java 鉁�
+鈹� 鈹� 鈹斺攢鈹� SignUtil.java 鉁�
+鈹� 鈹斺攢鈹� interfaces/
+鈹� 鈹溾攢鈹� controller/
+鈹� 鈹� 鈹溾攢鈹� PaymentController.java 鉁�
+鈹� 鈹� 鈹溾攢鈹� PaymentNotifyController.java 鉁�
+鈹� 鈹� 鈹斺攢鈹� HealthController.java 鉁�
+鈹� 鈹斺攢鈹� dto/
+鈹� 鈹溾攢鈹� PaymentRequest.java 鉁�
+鈹� 鈹斺攢鈹� PaymentResponse.java 鉁�
+鈹斺攢鈹� src/main/resources/
+ 鈹溾攢鈹� application.yml 鉁�
+ 鈹溾攢鈹� mapper/
+ 鈹� 鈹溾攢鈹� PaymentOrderMapper.xml 鉁�
+ 鈹� 鈹斺攢鈹� PaymentTransactionMapper.xml 鉁�
+ 鈹斺攢鈹� sql/
+ 鈹斺攢鈹� schema.sql 鉁�
+```
+
+**缁熻**:
+- Java鏂囦欢: 32涓� 鉁�
+- XML鏂囦欢: 2涓� 鉁�
+- 閰嶇疆鏂囦欢: 2涓� 鉁�
+- SQL鏂囦欢: 1涓� 鉁�
+- 鏂囨。: 3涓� 鉁�
+- **鎬昏: 40涓枃浠�** 鉁�
+
+## 馃殌 蹇�熷惎鍔ㄦ楠�
+
+### 1. 鍒濆鍖栨暟鎹簱
+```bash
+mysql -u root -p < src/main/resources/sql/schema.sql
+```
+
+### 2. 淇敼閰嶇疆
+缂栬緫 `src/main/resources/application.yml`:
+- 鏁版嵁搴撹繛鎺ヤ俊鎭�
+- 寰俊鏀粯閰嶇疆锛坅ppId, mchId, mchKey锛�
+- 鏀粯瀹濋厤缃紙appId, privateKey, alipayPublicKey锛�
+
+### 3. 缂栬瘧杩愯
+```bash
+mvn clean package
+java -jar target/dryad-payment-1.0.0.jar
+```
+
+### 4. 娴嬭瘯鎺ュ彛
+```bash
+# 鍋ュ悍妫�鏌�
+curl http://localhost:8080/api/health
+
+# 鍒涘缓寰俊鏀粯
+curl -X POST http://localhost:8080/api/pay/wechat/native \
+ -H "Content-Type: application/json" \
+ -d '{"bizOrderId":"TEST001","amount":100,"subject":"娴嬭瘯","callbackUrl":"http://test.com"}'
+```
+
+## 鈿欙笍 鎶�鏈爤鐗堟湰
+
+| 鎶�鏈� | 鐗堟湰 |
+|------|------|
+| Java | 1.8 |
+| Spring Boot | 2.5.15 |
+| MyBatis Plus | 3.4.3.4 |
+| Druid | 1.2.8 |
+| FastJSON | 1.2.83 |
+| Alipay SDK | 4.22.110.ALL |
+| ZXing | 3.4.1 |
+
+## 馃幆 涓嬩竴姝ュ缓璁�
+
+### 浼樺厛绾�1锛堟牳蹇冨姛鑳斤級
+1. 瀹炵幇寰俊鏀粯v2瀹㈡埛绔紙鍙傝�冨畼鏂规枃妗o級
+2. 瀹炵幇鏀粯瀹濆綋闈粯瀹㈡埛绔紙浣跨敤瀹樻柟SDK锛�
+3. 瀹屽杽鍥炶皟澶勭悊閫昏緫锛堢鍚嶉獙璇�+鐘舵�佹洿鏂帮級
+
+### 浼樺厛绾�2锛堜笟鍔″畬鏁存�э級
+4. 瀹炵幇涓氬姟鍥炶皟鏈嶅姟
+5. 瀹炵幇璁㈠崟杩囨湡瀹氭椂浠诲姟
+6. 娣诲姞鍗曞厓娴嬭瘯
+
+### 浼樺厛绾�3锛堣繍缁存敮鎸侊級
+7. 瀹炵幇姣忔棩瀵硅处浠诲姟
+8. 瀹炵幇绠$悊绔帴鍙�
+9. 娣诲姞鐩戞帶鍜屽憡璀�
+
+## 馃摑 娉ㄦ剰浜嬮」
+
+1. **瀹夊叏**: 閰嶇疆鏂囦欢涓殑瀵嗛挜璇峰嬁鎻愪氦鍒颁唬鐮佷粨搴�
+2. **HTTPS**: 鐢熶骇鐜鍥炶皟URL蹇呴』浣跨敤HTTPS
+3. **骞跺彂**: 寤鸿浣跨敤Redis鍒嗗竷寮忛攣
+4. **鐩戞帶**: 娣诲姞鏃ュ織鐩戞帶鍜屽紓甯稿憡璀�
+5. **娴嬭瘯**: 鍏堝湪娌欑鐜娴嬭瘯
+
+## 馃摉 鍙傝�冩枃妗�
+
+- 璁捐鏂规: `doc/璁捐鏂规.md`
+- 椤圭洰璇存槑: `README.md`
+- 寰俊鏀粯v2: https://pay.weixin.qq.com/wiki/doc/api/native.php
+- 鏀粯瀹濆綋闈粯: https://opendocs.alipay.com/open/194/105072
+
+---
+
+**浠g爜鐢熸垚瀹屾垚鏃堕棿**: 2025-11-23
+**椤圭洰鐘舵��**: 鍩虹妗嗘灦瀹屾垚锛屾牳蹇冧笟鍔″緟瀹炵幇
+**鍙繍琛屾��**: 鉁� 椤圭洰鍙紪璇戣繍琛岋紝鎺ュ彛鍙祴璇曪紙妯℃嫙妯″紡锛�
diff --git "a/dryad-payment/doc/\346\224\257\344\273\230\345\256\235\347\254\254\344\270\211\346\226\271\346\216\245\345\217\243-\345\277\253\351\200\237\345\274\200\345\247\213.md" "b/dryad-payment/doc/\346\224\257\344\273\230\345\256\235\347\254\254\344\270\211\346\226\271\346\216\245\345\217\243-\345\277\253\351\200\237\345\274\200\345\247\213.md"
new file mode 100644
index 0000000..8bca554
--- /dev/null
+++ "b/dryad-payment/doc/\346\224\257\344\273\230\345\256\235\347\254\254\344\270\211\346\226\271\346\216\245\345\217\243-\345\277\253\351\200\237\345\274\200\345\247\213.md"
@@ -0,0 +1,171 @@
+# 鏀粯瀹濈涓夋柟鎺ュ彛 - 蹇�熷紑濮嬫寚鍗�
+
+## 馃搵 鍔熻兘姒傝堪
+
+鏂板鏀粯瀹濆綋闈粯绗笁鏂规帴鍙o紝閫氳繃璋冪敤鏃х郴缁烶HP鎺ュ彛鐢熸垚鏀粯URL鍜屼簩缁寸爜銆�
+
+## 馃殌 蹇�熷紑濮�
+
+### 1. 鎺ュ彛鍦板潃
+```
+POST /api/pay/alipay/thirdparty/precreate
+```
+
+### 2. 璇锋眰绀轰緥
+```bash
+curl --location --request POST 'http://localhost:8080/api/pay/alipay/thirdparty/precreate' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "bizOrderId": "BF20250012-1",
+ "amount": 10000,
+ "subject": "鎬ユ晳杞繍鏈嶅姟璐�",
+ "description": "鎬ユ晳杞繍浠诲姟鏀粯",
+ "callbackUrl": "https://dsp.966120.com.cn/alipay/pay_notify"
+}'
+```
+
+### 3. 鍝嶅簲绀轰緥
+```json
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛",
+ "data": {
+ "orderId": 1234567890123456789,
+ "transactionId": 9876543210987654321,
+ "status": "PENDING",
+ "qrBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
+ "expireAt": "2025-11-24T18:00:00"
+ }
+}
+```
+
+## 馃搧 鏂板鏂囦欢娓呭崟
+
+### 鏍稿績浠g爜
+- 鉁� `AlipayThirdPartyClient.java` - 绗笁鏂规帴鍙h皟鐢ㄥ鎴风
+- 鉁� `PaymentService.java` - 鏂板绗笁鏂规敮浠樻柟娉�
+- 鉁� `PaymentController.java` - 鏂板绗笁鏂规敮浠樻帴鍙�
+
+### 鏂囨。
+- 鉁� `鏀粯瀹濈涓夋柟鎺ュ彛浣跨敤璇存槑.md` - 璇︾粏浣跨敤鏂囨。
+- 鉁� `鏀粯瀹濈涓夋柟鎺ュ彛娴嬭瘯鏂囨。.md` - 娴嬭瘯鎸囧崡
+- 鉁� `鏀粯瀹濈涓夋柟鎺ュ彛寮�鍙戞�荤粨.md` - 寮�鍙戞�荤粨
+
+## 馃攽 鏍稿績鍙傛暟璇存槑
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|--------|------|------|------|
+| bizOrderId | String | 鏄� | 涓氬姟璁㈠崟鍙� |
+| amount | Integer | 鏄� | 閲戦锛堝崟浣嶏細鍒嗭級 |
+| subject | String | 鏄� | 璁㈠崟鏍囬 |
+| description | String | 鍚� | 璁㈠崟鎻忚堪 |
+| callbackUrl | String | 鏄� | 鏀粯鍥炶皟鍦板潃 |
+
+## 鈿欙笍 閰嶇疆璇存槑
+
+宸插湪 `application.yml` 涓坊鍔犵涓夋柟鎺ュ彛閰嶇疆锛�
+
+```yaml
+payment:
+ alipay:
+ thirdParty:
+ enabled: true
+ url: https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php
+ defaultNotifyUrl: https://dsp.966120.com.cn/alipay/pay_notify
+ timeout: 30000
+```
+
+## 馃И 蹇�熸祴璇�
+
+### Postman娴嬭瘯
+1. 鍒涘缓POST璇锋眰锛歚http://localhost:8080/api/pay/alipay/thirdparty/precreate`
+2. 璁剧疆Header锛歚Content-Type: application/json`
+3. 璁剧疆Body锛圝SON鏍煎紡锛夛細
+```json
+{
+ "bizOrderId": "TEST001",
+ "amount": 100,
+ "subject": "娴嬭瘯璁㈠崟",
+ "callbackUrl": "https://dsp.966120.com.cn/alipay/pay_notify"
+}
+```
+4. 鍙戦�佽姹傦紝鑾峰彇浜岀淮鐮�
+
+### 鍓嶇灞曠ず浜岀淮鐮�
+```javascript
+const qrImage = document.getElementById('qrImage');
+qrImage.src = result.data.qrBase64;
+```
+
+## 馃攧 璋冪敤娴佺▼
+
+```
+鍓嶇 鈫� PaymentController
+ 鈫� PaymentService
+ 鈫� AlipayThirdPartyClient
+ 鈫� 绗笁鏂筆HP鎺ュ彛
+ 鈫� 杩斿洖鏀粯URL
+ 鈫� 鐢熸垚浜岀淮鐮�
+ 鈫� 杩斿洖缁欏墠绔�
+```
+
+## 馃搳 涓庢爣鍑嗘帴鍙e姣�
+
+| 鎺ュ彛绫诲瀷 | 鎺ュ彛璺緞 | 璋冪敤鏂瑰紡 |
+|---------|---------|---------|
+| 鏍囧噯鎺ュ彛 | `/api/pay/alipay/precreate` | 鏀粯瀹漇DK |
+| 绗笁鏂规帴鍙� | `/api/pay/alipay/thirdparty/precreate` | HTTP POST |
+
+## 鈿狅笍 娉ㄦ剰浜嬮」
+
+1. **閲戦鍗曚綅**锛氭墍鏈夐噾棰濆弬鏁板崟浣嶄负**鍒�**锛�1鍏� = 100鍒嗭級
+2. **璁㈠崟杩囨湡**锛氳鍗曢粯璁�**2灏忔椂**鍚庤繃鏈�
+3. **閲嶅璋冪敤**锛氱浉鍚屼笟鍔¤鍗曞彿杩斿洖宸插瓨鍦ㄨ褰�
+4. **鍥炶皟鍦板潃**锛氬繀椤婚厤缃纭殑鍥炶皟鍦板潃
+
+## 馃悰 闂鎺掓煡
+
+### 绗笁鏂规帴鍙h皟鐢ㄥけ璐�
+```bash
+# 妫�鏌ョ綉缁滆繛鎺�
+ping sys.966120.com.cn
+
+# 鐩存帴娴嬭瘯绗笁鏂规帴鍙�
+curl --location --request POST 'https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php' \
+--header 'Cookie: CAMEName=' \
+--form 'notify_url="https://dsp.966120.com.cn/alipay/pay_notify"' \
+--form 'out_trade_no="TEST001"' \
+--form 'total_fee="100"' \
+--form 'ServiceOrdID="TEST001"'
+```
+
+### 鏌ョ湅鏃ュ織
+```bash
+# 鏌ョ湅鏈嶅姟鏃ュ織
+tail -f logs/dryad-payment.log
+
+# 鍏抽敭鏃ュ織
+- "璋冪敤绗笁鏂规敮浠樺疂褰撻潰浠樻帴鍙�"
+- "绗笁鏂规帴鍙e搷搴�"
+- "鏀粯瀹濆綋闈粯锛堢涓夋柟鎺ュ彛锛夊垱寤烘垚鍔�"
+```
+
+## 馃摓 鎶�鏈敮鎸�
+
+璇︾粏鏂囨。鍙傝�冿細
+- `doc/鏀粯瀹濈涓夋柟鎺ュ彛浣跨敤璇存槑.md`
+- `doc/鏀粯瀹濈涓夋柟鎺ュ彛娴嬭瘯鏂囨。.md`
+- `doc/鏀粯瀹濈涓夋柟鎺ュ彛寮�鍙戞�荤粨.md`
+
+## 馃幆 涓嬩竴姝�
+
+- [ ] 娴嬭瘯绗笁鏂规帴鍙e彲鐢ㄦ��
+- [ ] 楠岃瘉杩斿洖URL鏍煎紡骞惰皟鏁磋В鏋愰�昏緫
+- [ ] 闆嗘垚鍒颁富搴旂敤
+- [ ] 閰嶇疆鐢熶骇鐜鍥炶皟鍦板潃
+- [ ] 杩涜瀹屾暣娴佺▼娴嬭瘯
+
+---
+
+**寮�鍙戝畬鎴愭椂闂�**: 2025-11-24
+**鐗堟湰**: v1.0.0
diff --git "a/dryad-payment/doc/\346\224\257\344\273\230\345\256\235\347\254\254\344\270\211\346\226\271\346\216\245\345\217\243\344\275\277\347\224\250\350\257\264\346\230\216.md" "b/dryad-payment/doc/\346\224\257\344\273\230\345\256\235\347\254\254\344\270\211\346\226\271\346\216\245\345\217\243\344\275\277\347\224\250\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..0e58d81
--- /dev/null
+++ "b/dryad-payment/doc/\346\224\257\344\273\230\345\256\235\347\254\254\344\270\211\346\226\271\346\216\245\345\217\243\344\275\277\347\224\250\350\257\264\346\230\216.md"
@@ -0,0 +1,199 @@
+# 鏀粯瀹濈涓夋柟鎺ュ彛浣跨敤璇存槑
+
+## 姒傝堪
+
+鏈ā鍧楁柊澧炰簡鏀粯瀹濆綋闈粯绗笁鏂规帴鍙o紝鐢ㄤ簬璋冪敤鏃х郴缁熺殑鏀粯瀹濇敮浠樻帴鍙g敓鎴愭敮浠楿RL鍜屼簩缁寸爜銆�
+
+## 鍔熻兘璇存槑
+
+### 鎺ュ彛鍦板潃
+- **Controller鎺ュ彛**: `POST /api/pay/alipay/thirdparty/precreate`
+- **绗笁鏂规帴鍙�**: `https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php`
+
+### 瀹炵幇娴佺▼
+
+1. 鎺ユ敹鍓嶇鏀粯璇锋眰
+2. 鍒涘缓鎴栨煡璇㈣鍗曡褰�
+3. 璋冪敤绗笁鏂规敮浠樺疂鎺ュ彛鐢熸垚鏀粯URL
+4. 灏嗘敮浠楿RL鐢熸垚浜岀淮鐮侊紙Base64鏍煎紡锛�
+5. 鍒涘缓浜ゆ槗璁板綍
+6. 杩斿洖鏀粯淇℃伅缁欏墠绔�
+
+## 璇锋眰鍙傛暟
+
+### 璇锋眰绀轰緥
+
+```json
+{
+ "bizOrderId": "BF20250012-1",
+ "amount": 10000,
+ "subject": "鎬ユ晳杞繍鏈嶅姟璐�",
+ "description": "鎬ユ晳杞繍浠诲姟鏀粯",
+ "callbackUrl": "https://dsp.966120.com.cn/alipay/pay_notify"
+}
+```
+
+### 鍙傛暟璇存槑
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|--------|------|------|------|
+| bizOrderId | String | 鏄� | 涓氬姟璁㈠崟鍙凤紙濡傦細BF20250012-1锛� |
+| amount | Integer | 鏄� | 璁㈠崟閲戦锛屽崟浣嶏細鍒嗭紙濡傦細10000琛ㄧず100.00鍏冿級 |
+| subject | String | 鏄� | 璁㈠崟鏍囬 |
+| description | String | 鍚� | 璁㈠崟鎻忚堪 |
+| callbackUrl | String | 鏄� | 鏀粯鎴愬姛鍚庣殑鍥炶皟閫氱煡鍦板潃 |
+
+## 鍝嶅簲鍙傛暟
+
+### 鎴愬姛鍝嶅簲绀轰緥
+
+```json
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛",
+ "data": {
+ "orderId": 1234567890123456789,
+ "transactionId": 9876543210987654321,
+ "status": "PENDING",
+ "qrBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
+ "expireAt": "2025-11-24T18:00:00"
+ }
+}
+```
+
+### 鍝嶅簲鍙傛暟璇存槑
+
+| 鍙傛暟鍚� | 绫诲瀷 | 璇存槑 |
+|--------|------|------|
+| orderId | Long | 鏀粯璁㈠崟ID |
+| transactionId | Long | 浜ゆ槗娴佹按ID |
+| status | String | 璁㈠崟鐘舵�侊紙PENDING-寰呮敮浠橈級 |
+| qrBase64 | String | 浜岀淮鐮佸浘鐗嘊ase64缂栫爜 |
+| expireAt | DateTime | 璁㈠崟杩囨湡鏃堕棿锛�2灏忔椂鍚庯級 |
+
+## 璋冪敤绗笁鏂规帴鍙e弬鏁版槧灏�
+
+璋冪敤绗笁鏂规帴鍙f椂锛屽弬鏁版槧灏勫叧绯诲涓嬶細
+
+| 绗笁鏂瑰弬鏁� | 鏉ユ簮 | 璇存槑 |
+|-----------|------|------|
+| notify_url | callbackUrl | 寮傛鍥炶皟閫氱煡鍦板潃 |
+| out_trade_no | orderId | 鏀粯妯″潡鐢熸垚鐨勮鍗旾D |
+| total_fee | amount | 璁㈠崟閲戦锛堝垎锛� |
+| ServiceOrdID | bizOrderId | 涓氬姟璁㈠崟鍙� |
+
+### curl绀轰緥
+
+```bash
+curl --location --request POST 'https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php' \
+--header 'Cookie: CAMEName=' \
+--form 'notify_url="https://dsp.966120.com.cn/alipay/pay_notify"' \
+--form 'out_trade_no="1234567890123456789"' \
+--form 'total_fee="10000"' \
+--form 'ServiceOrdID="BF20250012-1"'
+```
+
+## 浠g爜瀹炵幇
+
+### 鏍稿績绫昏鏄�
+
+#### 1. AlipayThirdPartyClient
+- **浣嶇疆**: `com.ruoyi.payment.infrastructure.channel.alipay.AlipayThirdPartyClient`
+- **鍔熻兘**: 灏佽绗笁鏂规敮浠樺疂鎺ュ彛璋冪敤閫昏緫
+- **涓昏鏂规硶**:
+ - `createQrCodeUrl()`: 璋冪敤绗笁鏂规帴鍙g敓鎴愭敮浠楿RL
+
+#### 2. PaymentService
+- **浣嶇疆**: `com.ruoyi.payment.application.service.PaymentService`
+- **鍔熻兘**: 鏀粯涓氬姟閫昏緫澶勭悊
+- **涓昏鏂规硶**:
+ - `createAlipayThirdPartyPrecreate()`: 鍙戣捣绗笁鏂规敮浠樺疂褰撻潰浠�
+
+#### 3. PaymentController
+- **浣嶇疆**: `com.ruoyi.payment.interfaces.controller.PaymentController`
+- **鍔熻兘**: 鎻愪緵HTTP鎺ュ彛
+- **鎺ュ彛璺緞**: `POST /api/pay/alipay/thirdparty/precreate`
+
+## 浣跨敤绀轰緥
+
+### 鍓嶇璋冪敤绀轰緥
+
+```javascript
+// 鍙戣捣鏀粯
+async function createPayment() {
+ const response = await fetch('/api/pay/alipay/thirdparty/precreate', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ bizOrderId: 'BF20250012-1',
+ amount: 10000,
+ subject: '鎬ユ晳杞繍鏈嶅姟璐�',
+ description: '鎬ユ晳杞繍浠诲姟鏀粯',
+ callbackUrl: 'https://dsp.966120.com.cn/alipay/pay_notify'
+ })
+ });
+
+ const result = await response.json();
+
+ if (result.code === 200) {
+ // 鏄剧ず浜岀淮鐮�
+ const qrImage = document.getElementById('qrImage');
+ qrImage.src = result.data.qrBase64;
+ }
+}
+```
+
+## 娉ㄦ剰浜嬮」
+
+1. **閲戦鍗曚綅**: 鎵�鏈夐噾棰濆弬鏁板崟浣嶅潎涓哄垎锛屽墠绔渶瑕佸皢鍏冭浆鎹负鍒嗭紙涔樹互100锛�
+2. **璁㈠崟杩囨湡**: 璁㈠崟榛樿2灏忔椂鍚庤繃鏈燂紝闇�鍦ㄦ湁鏁堟湡鍐呭畬鎴愭敮浠�
+3. **閲嶅璋冪敤**: 鍚屼竴涓氬姟璁㈠崟鍙峰娆¤皟鐢ㄤ細杩斿洖宸插瓨鍦ㄧ殑鏀粯璁板綍锛屼笉浼氶噸澶嶅垱寤�
+4. **鍥炶皟鍦板潃**: 蹇呴』閰嶇疆姝g‘鐨勫洖璋冨湴鍧�锛岀敤浜庢帴鏀舵敮浠樻垚鍔熼�氱煡
+5. **绗笁鏂规帴鍙e搷搴旀牸寮�**: 闇�瑕佹牴鎹疄闄呰繑鍥炴牸寮忚皟鏁� `parseQrCodeUrl()` 鏂规硶
+
+## 涓庢爣鍑嗘敮浠樺疂褰撻潰浠樻帴鍙g殑鍖哄埆
+
+| 鐗规�� | 鏍囧噯鎺ュ彛 | 绗笁鏂规帴鍙� |
+|------|---------|-----------|
+| 鎺ュ彛鍦板潃 | 鏀粯瀹濆畼鏂笰PI | 鏃х郴缁烶HP鎺ュ彛 |
+| 璋冪敤鏂瑰紡 | 鏀粯瀹漇DK | HTTP POST multipart/form-data |
+| 杩斿洖鍐呭 | 鏀粯瀹漲r_code | 鏀粯URL |
+| 浼樺娍 | 瀹樻柟鏀寔銆佺ǔ瀹� | 鍏煎鏃х郴缁熴�佸揩閫熼泦鎴� |
+
+## 閰嶇疆璇存槑
+
+### 榛樿鍥炶皟鍦板潃
+濡傛灉璇锋眰涓湭鎸囧畾`callbackUrl`锛岀郴缁熶細浣跨敤榛樿鍥炶皟鍦板潃锛�
+```
+https://dsp.966120.com.cn/alipay/pay_notify
+```
+
+鍙湪 `PaymentService.callAlipayThirdPartyPrecreate()` 鏂规硶涓慨鏀归粯璁ゅ�笺��
+
+## 閿欒澶勭悊
+
+### 甯歌閿欒鐮�
+
+| 閿欒鐮� | 璇存槑 | 澶勭悊鏂瑰紡 |
+|--------|------|---------|
+| 500 | 绗笁鏂规帴鍙h皟鐢ㄥけ璐� | 妫�鏌ョ綉缁滆繛鎺ュ拰绗笁鏂规帴鍙e彲鐢ㄦ�� |
+| 500 | 鏈繑鍥炴湁鏁堢殑鏀粯URL | 妫�鏌ョ涓夋柟鎺ュ彛杩斿洖鏍煎紡 |
+| 400 | 鍙傛暟鏍¢獙澶辫触 | 妫�鏌ヨ姹傚弬鏁版槸鍚﹀畬鏁� |
+
+## 娴嬭瘯寤鸿
+
+1. 鍏堜娇鐢≒ostman鎴朿url娴嬭瘯绗笁鏂规帴鍙e彲鐢ㄦ��
+2. 楠岃瘉绗笁鏂规帴鍙h繑鍥炵殑URL鏍煎紡
+3. 娴嬭瘯灏忛鏀粯锛堝1鍏冿級楠岃瘉瀹屾暣娴佺▼
+4. 娴嬭瘯璁㈠崟閲嶅鍒涘缓鍦烘櫙
+5. 娴嬭瘯璁㈠崟杩囨湡鍦烘櫙
+
+## 鍚庣画浼樺寲寤鸿
+
+1. **鍝嶅簲瑙f瀽**: 鏍规嵁绗笁鏂规帴鍙e疄闄呰繑鍥炴牸寮忥紝瀹屽杽 `parseQrCodeUrl()` 鏂规硶
+2. **閿欒閲嶈瘯**: 澧炲姞绗笁鏂规帴鍙h皟鐢ㄥけ璐ョ殑閲嶈瘯鏈哄埗
+3. **瓒呮椂鎺у埗**: 璁剧疆HTTP璇锋眰瓒呮椂鏃堕棿
+4. **鏃ュ織瀹屽杽**: 澧炲姞鏇磋缁嗙殑璋冪敤鏃ュ織锛屼究浜庨棶棰樻帓鏌�
+5. **鐩戞帶鍛婅**: 瀵规帴鍙h皟鐢ㄥけ璐ヨ繘琛岀洃鎺у拰鍛婅
diff --git "a/dryad-payment/doc/\346\224\257\344\273\230\345\256\235\347\254\254\344\270\211\346\226\271\346\216\245\345\217\243\345\274\200\345\217\221\346\200\273\347\273\223.md" "b/dryad-payment/doc/\346\224\257\344\273\230\345\256\235\347\254\254\344\270\211\346\226\271\346\216\245\345\217\243\345\274\200\345\217\221\346\200\273\347\273\223.md"
new file mode 100644
index 0000000..c715c21
--- /dev/null
+++ "b/dryad-payment/doc/\346\224\257\344\273\230\345\256\235\347\254\254\344\270\211\346\226\271\346\216\245\345\217\243\345\274\200\345\217\221\346\200\273\347\273\223.md"
@@ -0,0 +1,248 @@
+# 鏀粯瀹濈涓夋柟鎺ュ彛寮�鍙戞�荤粨
+
+## 寮�鍙戞杩�
+
+鏈寮�鍙戝湪鏀粯妯″潡涓柊澧炰簡鏀粯瀹濆綋闈粯绗笁鏂规帴鍙e姛鑳斤紝閫氳繃璋冪敤鏃х郴缁熺殑PHP鎺ュ彛鐢熸垚鏀粯URL鍜屼簩缁寸爜銆�
+
+## 鏂板鏂囦欢娓呭崟
+
+### 1. 鏍稿績浠g爜鏂囦欢
+
+#### AlipayThirdPartyClient.java
+**璺緞**: `dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/alipay/AlipayThirdPartyClient.java`
+
+**鍔熻兘**:
+- 灏佽绗笁鏂规敮浠樺疂鎺ュ彛璋冪敤
+- 浣跨敤Apache HttpClient鍙戦�乵ultipart/form-data璇锋眰
+- 瑙f瀽绗笁鏂规帴鍙e搷搴旓紝鎻愬彇鏀粯URL
+
+**鏍稿績鏂规硶**:
+```java
+public String createQrCodeUrl(String notifyUrl, String outTradeNo, Integer totalFee, String serviceOrdId)
+```
+
+### 2. 鏈嶅姟灞備慨鏀�
+
+#### PaymentService.java
+**璺緞**: `dryad-payment/src/main/java/com/ruoyi/payment/application/service/PaymentService.java`
+
+**鏂板鍐呭**:
+- 娉ㄥ叆 `AlipayThirdPartyClient` Bean
+- 鏂板鏂规硶 `createAlipayThirdPartyPrecreate()` - 澶勭悊绗笁鏂规敮浠樺疂褰撻潰浠樹笟鍔¢�昏緫
+- 鏂板鏂规硶 `callAlipayThirdPartyPrecreate()` - 璋冪敤绗笁鏂规帴鍙g敓鎴愭敮浠楿RL
+
+### 3. 鎺у埗鍣ㄥ眰淇敼
+
+#### PaymentController.java
+**璺緞**: `dryad-payment/src/main/java/com/ruoyi/payment/interfaces/controller/PaymentController.java`
+
+**鏂板鎺ュ彛**:
+- `POST /api/pay/alipay/thirdparty/precreate` - 鍙戣捣鏀粯瀹濆綋闈粯锛堢涓夋柟鎺ュ彛锛�
+
+### 4. 鏂囨。鏂囦欢
+
+#### 鏀粯瀹濈涓夋柟鎺ュ彛浣跨敤璇存槑.md
+**璺緞**: `dryad-payment/doc/鏀粯瀹濈涓夋柟鎺ュ彛浣跨敤璇存槑.md`
+
+**鍐呭**:
+- 鍔熻兘璇存槑
+- 鎺ュ彛鍙傛暟鏂囨。
+- 浠g爜瀹炵幇璇存槑
+- 浣跨敤绀轰緥
+- 娉ㄦ剰浜嬮」
+
+#### 鏀粯瀹濈涓夋柟鎺ュ彛娴嬭瘯鏂囨。.md
+**璺緞**: `dryad-payment/doc/鏀粯瀹濈涓夋柟鎺ュ彛娴嬭瘯鏂囨。.md`
+
+**鍐呭**:
+- Postman娴嬭瘯姝ラ
+- cURL娴嬭瘯鍛戒护
+- 鍚勭娴嬭瘯鍦烘櫙
+- 鍓嶇闆嗘垚绀轰緥
+- 闂鎺掓煡鎸囧崡
+
+## 鎶�鏈疄鐜拌鐐�
+
+### 1. HTTP璇锋眰灏佽
+浣跨敤Apache HttpClient鍙戦�乵ultipart/form-data鏍煎紡鐨凱OST璇锋眰锛�
+```java
+HttpEntity entity = MultipartEntityBuilder.create()
+ .addTextBody("notify_url", notifyUrl)
+ .addTextBody("out_trade_no", outTradeNo)
+ .addTextBody("total_fee", String.valueOf(totalFee))
+ .addTextBody("ServiceOrdID", serviceOrdId)
+ .build();
+```
+
+### 2. 鍙傛暟鏄犲皠
+| 涓氬姟鍙傛暟 | 绗笁鏂瑰弬鏁� | 璇存槑 |
+|---------|-----------|------|
+| callbackUrl | notify_url | 鍥炶皟閫氱煡鍦板潃 |
+| orderId | out_trade_no | 璁㈠崟ID |
+| amount | total_fee | 閲戦锛堝垎锛� |
+| bizOrderId | ServiceOrdID | 涓氬姟璁㈠崟鍙� |
+
+### 3. URL瑙f瀽
+鎻愪緵浜嗙伒娲荤殑URL瑙f瀽鏂规硶锛屾敮鎸侊細
+- 鐩存帴杩斿洖URL瀛楃涓�
+- JSON鏍煎紡鍝嶅簲
+- 甯﹀紩鍙风殑URL瀛楃涓�
+
+鍙牴鎹疄闄呰繑鍥炴牸寮忚皟鏁� `parseQrCodeUrl()` 鏂规硶銆�
+
+### 4. 浜岀淮鐮佺敓鎴�
+澶嶇敤鐜版湁鐨� `QrCodeUtil` 宸ュ叿绫伙紝灏嗘敮浠楿RL杞崲涓築ase64鏍煎紡鐨勪簩缁寸爜鍥剧墖銆�
+
+## 鎺ュ彛璋冪敤娴佺▼
+
+```
+鍓嶇璇锋眰
+ 鈫�
+PaymentController.createAlipayThirdPartyPrecreate()
+ 鈫�
+PaymentService.createAlipayThirdPartyPrecreate()
+ 鈫�
+callAlipayThirdPartyPrecreate()
+ 鈫�
+AlipayThirdPartyClient.createQrCodeUrl()
+ 鈫�
+HTTP POST 鈫� https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php
+ 鈫�
+瑙f瀽鍝嶅簲寰楀埌鏀粯URL
+ 鈫�
+QrCodeUtil.generateQrCodeBase64() - 鐢熸垚浜岀淮鐮�
+ 鈫�
+淇濆瓨璁㈠崟鍜屼氦鏄撹褰�
+ 鈫�
+杩斿洖鏀粯淇℃伅缁欏墠绔�
+```
+
+## 涓庢爣鍑嗘帴鍙g殑瀵规瘮
+
+| 瀵规瘮椤� | 鏍囧噯鏀粯瀹漇DK鎺ュ彛 | 绗笁鏂规帴鍙� |
+|-------|----------------|----------|
+| 瀹炵幇绫� | AlipayF2FClient | AlipayThirdPartyClient |
+| 璋冪敤鏂瑰紡 | 鏀粯瀹漇DK | HTTP POST |
+| 鎺ュ彛鍦板潃 | 鏀粯瀹濆畼鏂� | 鏃х郴缁烶HP鎺ュ彛 |
+| 杩斿洖鍐呭 | qr_code | 鏀粯URL |
+| 鎺ュ彛璺緞 | /api/pay/alipay/precreate | /api/pay/alipay/thirdparty/precreate |
+
+## 鏁版嵁搴撹〃璁捐
+
+### 璁㈠崟琛� (pay_order)
+- 璁板綍鏀粯璁㈠崟鍩烘湰淇℃伅
+- 鏀寔澶氭笭閬擄紙寰俊銆佹敮浠樺疂锛�
+- 2灏忔椂鑷姩杩囨湡
+
+### 浜ゆ槗琛� (pay_transaction)
+- 璁板綍姣忕瑪浜ゆ槗璇︽儏
+- 瀛樺偍鏀粯URL鍜屼簩缁寸爜
+- 鍏宠仈璁㈠崟ID
+
+## 閰嶇疆璇存槑
+
+### 榛樿閰嶇疆
+- 鍥炶皟鍦板潃: `https://dsp.966120.com.cn/alipay/pay_notify`
+- 浜岀淮鐮佸昂瀵�: 300x300
+- 璁㈠崟杩囨湡鏃堕棿: 2灏忔椂
+
+### 鍙厤缃」
+鍦� `PaymentService.callAlipayThirdPartyPrecreate()` 鏂规硶涓彲淇敼锛�
+- 榛樿鍥炶皟鍦板潃
+- 鍏朵粬涓氬姟鍙傛暟
+
+## 娴嬭瘯瑕佺偣
+
+### 鍔熻兘娴嬭瘯
+- [x] 姝e父鏀粯娴佺▼
+- [x] 閲嶅璁㈠崟澶勭悊
+- [x] 鍙傛暟鏍¢獙
+- [x] 璁㈠崟杩囨湡澶勭悊
+
+### 鎺ュ彛娴嬭瘯
+- [ ] 绗笁鏂规帴鍙e彲鐢ㄦ��
+- [ ] 缃戠粶寮傚父澶勭悊
+- [ ] 瓒呮椂澶勭悊
+- [ ] 杩斿洖鏍煎紡瑙f瀽
+
+### 闆嗘垚娴嬭瘯
+- [ ] 涓庝富搴旂敤闆嗘垚
+- [ ] 鏀粯鍥炶皟澶勭悊
+- [ ] 鍓嶇灞曠ず浜岀淮鐮�
+
+## 娉ㄦ剰浜嬮」
+
+### 1. 鍝嶅簲鏍煎紡閫傞厤
+绗笁鏂规帴鍙g殑瀹為檯杩斿洖鏍煎紡闇�瑕佹牴鎹疄闄呮儏鍐佃皟鏁� `parseQrCodeUrl()` 鏂规硶锛�
+
+```java
+private String parseQrCodeUrl(String responseBody) {
+ // 鏍规嵁瀹為檯杩斿洖鏍煎紡璋冩暣
+ // 濡傛灉鏄疛SON: JSONObject json = JSON.parseObject(responseBody);
+ // 濡傛灉鏄函鏂囨湰: return responseBody.trim();
+}
+```
+
+### 2. 閿欒澶勭悊
+寤鸿娣诲姞鏇磋缁嗙殑閿欒澶勭悊鍜屾棩蹇楄褰曪紝渚夸簬闂鎺掓煡銆�
+
+### 3. 鎬ц兘浼樺寲
+鑰冭檻娣诲姞锛�
+- HTTP杩炴帴姹�
+- 瓒呮椂璁剧疆
+- 閲嶈瘯鏈哄埗
+- 鐔旀柇闄嶇骇
+
+### 4. 瀹夊叏鎬�
+- 楠岃瘉鍥炶皟绛惧悕
+- HTTPS閫氫俊
+- 鏁忔劅淇℃伅鍔犲瘑
+
+## 鍚庣画浼樺寲寤鸿
+
+### 鐭湡浼樺寲
+1. 鏍规嵁瀹為檯杩斿洖鏍煎紡瀹屽杽URL瑙f瀽閫昏緫
+2. 娣诲姞鍗曞厓娴嬭瘯
+3. 瀹屽杽寮傚父澶勭悊
+4. 澧炲姞鎺ュ彛鏃ュ織
+
+### 闀挎湡浼樺寲
+1. 寮曞叆HTTP杩炴帴姹犵鐞�
+2. 瀹炵幇閲嶈瘯鍜岀啍鏂満鍒�
+3. 娣诲姞鎺ュ彛鐩戞帶鍜屽憡璀�
+4. 鎬ц兘浼樺寲鍜屽帇娴�
+5. 鏀寔寮傛澶勭悊
+
+## 渚濊禆璇存槑
+
+椤圭洰宸插寘鍚墍闇�渚濊禆锛屾棤闇�棰濆娣诲姞锛�
+- `org.apache.httpcomponents:httpclient:4.5.13`
+- `com.google.zxing:core:3.4.1`
+- `com.google.zxing:javase:3.4.1`
+
+## 閮ㄧ讲璇存槑
+
+### 寮�鍙戠幆澧�
+1. 纭繚鏁版嵁搴撻厤缃纭�
+2. 楠岃瘉绗笁鏂规帴鍙e彲璁块棶
+3. 鍚姩鏈嶅姟娴嬭瘯
+
+### 鐢熶骇鐜
+1. 閰嶇疆姝g‘鐨勫洖璋冨湴鍧�
+2. 璁剧疆鍚堢悊鐨勮秴鏃舵椂闂�
+3. 娣诲姞鐩戞帶鍜屽憡璀�
+4. 杩涜鍘嬪姏娴嬭瘯
+
+## 鑱旂郴鏂瑰紡
+
+濡傛湁闂锛岃鏌ラ槄锛�
+- 浣跨敤璇存槑鏂囨。: `doc/鏀粯瀹濈涓夋柟鎺ュ彛浣跨敤璇存槑.md`
+- 娴嬭瘯鏂囨。: `doc/鏀粯瀹濈涓夋柟鎺ュ彛娴嬭瘯鏂囨。.md`
+
+## 鐗堟湰鍘嗗彶
+
+### v1.0.0 (2025-11-24)
+- 鉁� 鏂板鏀粯瀹濈涓夋柟鎺ュ彛鍔熻兘
+- 鉁� 鏀寔璋冪敤鏃х郴缁烶HP鎺ュ彛
+- 鉁� 鐢熸垚鏀粯URL鍜屼簩缁寸爜
+- 鉁� 瀹屽杽鏂囨。鍜屾祴璇曠敤渚�
diff --git "a/dryad-payment/doc/\346\224\257\344\273\230\345\256\235\347\254\254\344\270\211\346\226\271\346\216\245\345\217\243\346\265\213\350\257\225\346\226\207\346\241\243.md" "b/dryad-payment/doc/\346\224\257\344\273\230\345\256\235\347\254\254\344\270\211\346\226\271\346\216\245\345\217\243\346\265\213\350\257\225\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..b24c4ff
--- /dev/null
+++ "b/dryad-payment/doc/\346\224\257\344\273\230\345\256\235\347\254\254\344\270\211\346\226\271\346\216\245\345\217\243\346\265\213\350\257\225\346\226\207\346\241\243.md"
@@ -0,0 +1,320 @@
+# 鏀粯瀹濈涓夋柟鎺ュ彛API娴嬭瘯鏂囨。
+
+## 娴嬭瘯鐜鍑嗗
+
+### 1. 鍚姩鏀粯妯″潡鏈嶅姟
+```bash
+cd dryad-payment
+mvn spring-boot:run
+```
+
+鏈嶅姟榛樿绔彛锛歚8080`锛堟牴鎹疄闄呴厤缃皟鏁达級
+
+## 鎺ュ彛娴嬭瘯
+
+### 鎺ュ彛淇℃伅
+- **璇锋眰鍦板潃**: `POST http://localhost:8080/api/pay/alipay/thirdparty/precreate`
+- **Content-Type**: `application/json`
+
+### Postman娴嬭瘯姝ラ
+
+#### 1. 鍒涘缓鏂拌姹�
+- 鏂规硶锛歚POST`
+- URL锛歚http://localhost:8080/api/pay/alipay/thirdparty/precreate`
+
+#### 2. 璁剧疆璇锋眰澶�
+```
+Content-Type: application/json
+```
+
+#### 3. 璁剧疆璇锋眰浣擄紙Body -> raw -> JSON锛�
+```json
+{
+ "bizOrderId": "BF20250012-1",
+ "amount": 10000,
+ "subject": "鎬ユ晳杞繍鏈嶅姟璐�",
+ "description": "鎬ユ晳杞繍浠诲姟鏀粯",
+ "callbackUrl": "https://dsp.966120.com.cn/alipay/pay_notify"
+}
+```
+
+#### 4. 鍙戦�佽姹�
+
+棰勬湡鍝嶅簲锛�
+```json
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛",
+ "data": {
+ "orderId": 1234567890123456789,
+ "transactionId": 9876543210987654321,
+ "status": "PENDING",
+ "qrBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
+ "expireAt": "2025-11-24T18:00:00"
+ }
+}
+```
+
+## cURL娴嬭瘯鍛戒护
+
+### 鍩烘湰娴嬭瘯
+```bash
+curl --location --request POST 'http://localhost:8080/api/pay/alipay/thirdparty/precreate' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "bizOrderId": "BF20250012-1",
+ "amount": 10000,
+ "subject": "鎬ユ晳杞繍鏈嶅姟璐�",
+ "description": "鎬ユ晳杞繍浠诲姟鏀粯",
+ "callbackUrl": "https://dsp.966120.com.cn/alipay/pay_notify"
+}'
+```
+
+### 娴嬭瘯涓嶅悓閲戦
+```bash
+# 娴嬭瘯1鍏冿紙100鍒嗭級
+curl --location --request POST 'http://localhost:8080/api/pay/alipay/thirdparty/precreate' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "bizOrderId": "TEST001",
+ "amount": 100,
+ "subject": "娴嬭瘯璁㈠崟",
+ "description": "娴嬭瘯1鍏冩敮浠�",
+ "callbackUrl": "https://dsp.966120.com.cn/alipay/pay_notify"
+}'
+```
+
+## 娴嬭瘯鍦烘櫙
+
+### 鍦烘櫙1锛氭甯告敮浠樻祦绋�
+1. 鍙戦�佹敮浠樿姹�
+2. 鑾峰彇璁㈠崟ID鍜屼簩缁寸爜
+3. 鍓嶇灞曠ず浜岀淮鐮�
+4. 鐢ㄦ埛鎵爜鏀粯
+5. 鎺ユ敹鏀粯鍥炶皟閫氱煡
+
+### 鍦烘櫙2锛氶噸澶嶈鍗曟祴璇�
+```bash
+# 绗竴娆¤姹�
+curl --location --request POST 'http://localhost:8080/api/pay/alipay/thirdparty/precreate' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "bizOrderId": "DUPLICATE_TEST_001",
+ "amount": 100,
+ "subject": "閲嶅璁㈠崟娴嬭瘯",
+ "callbackUrl": "https://dsp.966120.com.cn/alipay/pay_notify"
+}'
+
+# 绗簩娆¤姹傦紙鐩稿悓bizOrderId锛�- 搴旇杩斿洖鐩稿悓鐨勮鍗曚俊鎭�
+curl --location --request POST 'http://localhost:8080/api/pay/alipay/thirdparty/precreate' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "bizOrderId": "DUPLICATE_TEST_001",
+ "amount": 100,
+ "subject": "閲嶅璁㈠崟娴嬭瘯",
+ "callbackUrl": "https://dsp.966120.com.cn/alipay/pay_notify"
+}'
+```
+
+### 鍦烘櫙3锛氬弬鏁版牎楠屾祴璇�
+
+#### 缂哄皯蹇呭~鍙傛暟
+```bash
+curl --location --request POST 'http://localhost:8080/api/pay/alipay/thirdparty/precreate' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "bizOrderId": "TEST001",
+ "subject": "娴嬭瘯璁㈠崟"
+}'
+```
+棰勬湡锛氳繑鍥�400閿欒锛屾彁绀�"閲戦涓嶈兘涓虹┖"
+
+#### 閲戦涓�0
+```bash
+curl --location --request POST 'http://localhost:8080/api/pay/alipay/thirdparty/precreate' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "bizOrderId": "TEST001",
+ "amount": 0,
+ "subject": "娴嬭瘯璁㈠崟",
+ "callbackUrl": "https://dsp.966120.com.cn/alipay/pay_notify"
+}'
+```
+棰勬湡锛氳繑鍥�400閿欒锛屾彁绀�"閲戦蹇呴』澶т簬0"
+
+## 鏌ヨ璁㈠崟鎺ュ彛娴嬭瘯
+
+### 鏍规嵁璁㈠崟ID鏌ヨ
+```bash
+curl --location --request GET 'http://localhost:8080/api/pay/orders/{orderId}'
+```
+
+绀轰緥锛�
+```bash
+curl --location --request GET 'http://localhost:8080/api/pay/orders/1234567890123456789'
+```
+
+### 鏌ヨ鏈�鏂颁氦鏄撹褰�
+```bash
+curl --location --request GET 'http://localhost:8080/api/pay/orders/{orderId}/transactions/latest'
+```
+
+绀轰緥锛�
+```bash
+curl --location --request GET 'http://localhost:8080/api/pay/orders/1234567890123456789/transactions/latest'
+```
+
+## 绗笁鏂规帴鍙g洿鎺ユ祴璇�
+
+濡傛灉闇�瑕佸崟鐙祴璇曠涓夋柟鎺ュ彛鏄惁鍙敤锛�
+
+```bash
+curl --location --request POST 'https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php' \
+--header 'Cookie: CAMEName=' \
+--form 'notify_url="https://dsp.966120.com.cn/alipay/pay_notify"' \
+--form 'out_trade_no="T202511240001"' \
+--form 'total_fee="10000"' \
+--form 'ServiceOrdID="BF20250012-1"'
+```
+
+## 鏃ュ織鏌ョ湅
+
+鏌ョ湅鏈嶅姟鏃ュ織锛屽叧娉ㄤ互涓嬪叧閿棩蹇楋細
+
+```
+# 鎺ュ彛璋冪敤
+鍙戣捣鏀粯瀹濆綋闈粯锛堢涓夋柟鎺ュ彛锛夛紝璇锋眰鍙傛暟: {...}
+
+# 绗笁鏂规帴鍙h皟鐢�
+璋冪敤绗笁鏂规敮浠樺疂褰撻潰浠樻帴鍙o紝璁㈠崟鍙�: xxx, 閲戦: xxx鍒�, 涓氬姟璁㈠崟ID: xxx
+鍙戦�佽姹傚埌绗笁鏂规帴鍙�: https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php
+
+# 绗笁鏂规帴鍙e搷搴�
+绗笁鏂规帴鍙e搷搴旓紝鐘舵�佺爜: 200, 鍝嶅簲鍐呭: xxx
+绗笁鏂规敮浠樺疂褰撻潰浠楿RL鐢熸垚鎴愬姛: xxx
+
+# 璁㈠崟鍒涘缓鎴愬姛
+鏀粯瀹濆綋闈粯锛堢涓夋柟鎺ュ彛锛夊垱寤烘垚鍔燂紝璁㈠崟ID: xxx, 浜ゆ槗ID: xxx
+```
+
+## 甯歌闂鎺掓煡
+
+### 1. 绗笁鏂规帴鍙h皟鐢ㄥけ璐�
+- 妫�鏌ョ綉缁滆繛鎺�
+- 楠岃瘉绗笁鏂规帴鍙f槸鍚﹀彲璁块棶
+- 妫�鏌ヨ姹傚弬鏁版牸寮忔槸鍚︽纭�
+
+### 2. 鏈繑鍥炴湁鏁堢殑鏀粯URL
+- 妫�鏌ョ涓夋柟鎺ュ彛杩斿洖鍐呭
+- 璋冩暣 `parseQrCodeUrl()` 鏂规硶浠ラ�傞厤瀹為檯杩斿洖鏍煎紡
+
+### 3. 浜岀淮鐮佺敓鎴愬け璐�
+- 妫�鏌ユ敮浠楿RL鏍煎紡鏄惁姝g‘
+- 楠岃瘉ZXing搴撴槸鍚︽甯稿伐浣�
+
+## 鏁版嵁搴撻獙璇�
+
+### 鏌ヨ璁㈠崟琛�
+```sql
+SELECT * FROM pay_order
+WHERE biz_order_id = 'BF20250012-1'
+ORDER BY created_at DESC;
+```
+
+### 鏌ヨ浜ゆ槗琛�
+```sql
+SELECT * FROM pay_transaction
+WHERE order_id = '璁㈠崟ID'
+ORDER BY created_at DESC;
+```
+
+## 鎬ц兘娴嬭瘯
+
+### 骞跺彂娴嬭瘯锛堜娇鐢ˋpache Bench锛�
+```bash
+# 100涓姹傦紝10涓苟鍙�
+ab -n 100 -c 10 -p payload.json -T application/json \
+http://localhost:8080/api/pay/alipay/thirdparty/precreate
+```
+
+payload.json:
+```json
+{
+ "bizOrderId": "PERF_TEST_${RANDOM}",
+ "amount": 100,
+ "subject": "鎬ц兘娴嬭瘯",
+ "callbackUrl": "https://dsp.966120.com.cn/alipay/pay_notify"
+}
+```
+
+## 鍓嶇闆嗘垚绀轰緥
+
+### HTML + JavaScript
+```html
+<!DOCTYPE html>
+<html>
+<head>
+ <title>鏀粯瀹濇敮浠樻祴璇�</title>
+</head>
+<body>
+ <h1>鏀粯瀹濇敮浠樻祴璇�</h1>
+ <button onclick="createPayment()">鍙戣捣鏀粯</button>
+ <div id="qrcode" style="margin-top: 20px;"></div>
+
+ <script>
+ async function createPayment() {
+ try {
+ const response = await fetch('http://localhost:8080/api/pay/alipay/thirdparty/precreate', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ bizOrderId: 'WEB_TEST_' + Date.now(),
+ amount: 100,
+ subject: '娴嬭瘯鏀粯',
+ description: '缃戦〉娴嬭瘯',
+ callbackUrl: 'https://dsp.966120.com.cn/alipay/pay_notify'
+ })
+ });
+
+ const result = await response.json();
+
+ if (result.code === 200) {
+ // 鏄剧ず浜岀淮鐮�
+ const qrcodeDiv = document.getElementById('qrcode');
+ qrcodeDiv.innerHTML = `
+ <p>璁㈠崟ID: ${result.data.orderId}</p>
+ <p>浜ゆ槗ID: ${result.data.transactionId}</p>
+ <p>杩囨湡鏃堕棿: ${result.data.expireAt}</p>
+ <img src="${result.data.qrBase64}" alt="鏀粯浜岀淮鐮�" />
+ `;
+ } else {
+ alert('鏀粯鍒涘缓澶辫触: ' + result.msg);
+ }
+ } catch (error) {
+ alert('璇锋眰澶辫触: ' + error.message);
+ }
+ }
+ </script>
+</body>
+</html>
+```
+
+## 鐜閰嶇疆妫�鏌ユ竻鍗�
+
+- [ ] 鏀粯妯″潡鏈嶅姟宸插惎鍔�
+- [ ] 鏁版嵁搴撹繛鎺ユ甯�
+- [ ] 绗笁鏂规帴鍙e彲璁块棶
+- [ ] 鍥炶皟鍦板潃宸查厤缃�
+- [ ] 鏃ュ織绾у埆璁剧疆涓篋EBUG锛堝紑鍙戠幆澧冿級
+- [ ] 缃戠粶闃茬伀澧欏厑璁歌闂涓夋柟鎺ュ彛
+
+## 涓嬩竴姝�
+
+娴嬭瘯閫氳繃鍚庯紝鍙互杩涜浠ヤ笅宸ヤ綔锛�
+1. 闆嗘垚鍒颁富搴旂敤涓�
+2. 瀹屽杽閿欒澶勭悊
+3. 娣诲姞鐩戞帶鍜屽憡璀�
+4. 鎬ц兘浼樺寲
+5. 瀹夊叏鍔犲浐
diff --git "a/dryad-payment/doc/\350\256\276\350\256\241\346\226\271\346\241\210.md" "b/dryad-payment/doc/\350\256\276\350\256\241\346\226\271\346\241\210.md"
new file mode 100644
index 0000000..1fd77dc
--- /dev/null
+++ "b/dryad-payment/doc/\350\256\276\350\256\241\346\226\271\346\241\210.md"
@@ -0,0 +1,1067 @@
+# 鏀粯妯″潡璁捐鏂规锛圖DD鏋舵瀯锛�
+
+## 涓�銆佹暣浣撴灦鏋勪笌鎶�鏈爤
+
+### 鎶�鏈爤
+- **Java**: 1.8
+- **妗嗘灦**: Spring Boot 2.5.15 + ruoyi-framework
+- **鎸佷箙鍖�**: MyBatis / MyBatis-Plus
+- **鏋舵瀯妯″紡**: DDD锛堥鍩熼┍鍔ㄨ璁★級
+- **鏀粯娓犻亾**: 寰俊鏀粯v2銆佹敮浠樺疂褰撻潰浠�
+
+### 鏈嶅姟瀹氫綅
+- 鐙珛鏈嶅姟锛屽彲鍗曠嫭杩愯
+- 鍗曞晢鎴锋ā寮�
+- 閰嶇疆缁熶竴鍦� `application.yml` 绠$悊
+
+---
+
+## 浜屻�丏DD鍒嗗眰涓庡寘缁撴瀯
+
+### 鍒嗗眰鏄犲皠鑷� RuoYi 缁撴瀯
+
+#### 1. Interfaces 灞傦紙鎺ュ彛灞傦級
+**鍖呰矾寰�**: `com.ruoyi.payment.interfaces.controller`
+
+**鑱岃矗**:
+- 瀵瑰 REST API锛堝彂璧锋敮浠樸�佹煡璇㈣鍗曪級
+- 娓犻亾寮傛鍥炶皟 Webhook锛堝井淇�/鏀粯瀹濓級
+- 绠$悊绔搷浣滄帴鍙o紙鍏抽棴銆侀噸鍙戝洖璋冦�佸璐︽煡璇級
+
+#### 2. Application 灞傦紙搴旂敤灞傦級
+**鍖呰矾寰�**: `com.ruoyi.payment.application.service`
+
+**鑱岃矗**:
+- 鐢ㄤ緥缂栨帓锛堝彂璧锋敮浠樸�佸鐞嗘笭閬撳洖璋冦�佽Е鍙戜笟鍔″洖璋冿級
+- 浜嬪姟鎺у埗涓庤法鑱氬悎鍗忚皟
+- 瀹氭椂浠诲姟锛堟瘡鏃ュ璐︺�佽鍗曡繃鏈熷鐞嗭級
+
+#### 3. Domain 灞傦紙棰嗗煙灞傦級
+**鍖呰矾寰�**:
+- `com.ruoyi.payment.domain.model` - 鑱氬悎鏍广�佸疄浣撱�佸�煎璞�
+- `com.ruoyi.payment.domain.service` - 棰嗗煙鏈嶅姟
+- `com.ruoyi.payment.domain.repository` - 浠撳偍鎺ュ彛
+
+**鑱岃矗**:
+- 鏍稿績涓氬姟瑙勫垯涓庣姸鎬佹満
+- 鑱氬悎鏍瑰皝瑁呬笌涓嶅彉鎬х害鏉�
+- 棰嗗煙浜嬩欢瀹氫箟
+
+#### 4. Infrastructure 灞傦紙鍩虹璁炬柦灞傦級
+**鍖呰矾寰�**:
+- `com.ruoyi.payment.infrastructure.persistence` - Mapper 瀹炵幇
+- `com.ruoyi.payment.infrastructure.channel.wechat` - 寰俊瀹㈡埛绔�
+- `com.ruoyi.payment.infrastructure.channel.alipay` - 鏀粯瀹濆鎴风
+- `com.ruoyi.payment.infrastructure.config` - 閰嶇疆鍔犺浇
+- `com.ruoyi.payment.infrastructure.schedule` - 瀹氭椂璋冨害
+- `com.ruoyi.payment.infrastructure.audit` - 鎿嶄綔瀹¤
+
+**鑱岃矗**:
+- 浠撳偍鎺ュ彛瀹炵幇锛堟暟鎹簱璁块棶锛�
+- 娓犻亾瀹㈡埛绔皝瑁呬笌閫傞厤
+- 璇佷功涓庡瘑閽ョ鐞�
+- 瀹氭椂浠诲姟涓庢秷鎭彂甯�
+
+---
+
+## 涓夈�佹牳蹇冮鍩熸ā鍨�
+
+### 1. PaymentOrder锛堟敮浠樿鍗� - 鑱氬悎鏍癸級
+
+**瀛楁**:
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 鍐呴儴璁㈠崟ID锛堜富閿紝闆姳ID锛� |
+| bizOrderId | String | 澶栭儴涓氬姟璁㈠崟鍙� |
+| amount | Integer | 閲戦锛堝崟浣嶏細鍒嗭級 |
+| currency | String | 甯佺锛堝浐瀹� CNY锛� |
+| channel | Enum | 鏀粯娓犻亾锛圵ECHAT/ALIPAY锛� |
+| status | Enum | 璁㈠崟鐘舵�� |
+| subject | String | 璁㈠崟鏍囬 |
+| description | String | 璁㈠崟鎻忚堪 |
+| callbackUrl | String | 涓氬姟鍥炶皟鍦板潃 |
+| expireAt | DateTime | 杩囨湡鏃堕棿锛堝垱寤哄悗+2灏忔椂锛� |
+| latestTransactionId | Long | 鏈�鏂颁氦鏄揑D |
+| channelTradeNo | String | 娓犻亾浜ゆ槗鍙凤紙鏀粯鎴愬姛鍚庯級 |
+| paidAt | DateTime | 鏀粯鎴愬姛鏃堕棿 |
+| version | Integer | 涔愯閿佺増鏈彿 |
+| createdAt | DateTime | 鍒涘缓鏃堕棿 |
+| updatedAt | DateTime | 鏇存柊鏃堕棿 |
+
+**璁㈠崟鐘舵�佹灇涓�** (`status`):
+- `INIT`: 鍒濆鍖�
+- `PENDING`: 寰呮敮浠�
+- `SUCCEEDED`: 鏀粯鎴愬姛
+- `FAILED`: 鏀粯澶辫触
+- `CANCELED`: 宸插叧闂�
+- `EXPIRED`: 宸茶繃鏈�
+
+**涓氬姟瑙勫垯**:
+1. 閲戦蹇呴』 > 0
+2. 甯佺鍥哄畾涓� CNY
+3. 鍚屼竴璁㈠崟鍚屾椂鍙厑璁稿瓨鍦�**涓�绗旇繘琛屼腑鐨勪氦鏄�**锛圥ENDING锛�
+4. 璁㈠崟瓒呰繃2灏忔椂鏈敮浠樿嚜鍔ㄦ爣璁颁负 EXPIRED
+5. 璁㈠崟鎴愬姛鍚庤Е鍙戝閮ㄤ笟鍔″洖璋�
+
+### 2. PaymentTransaction锛堟敮浠樹氦鏄撴祦姘� - 瀹炰綋锛�
+
+**瀛楁**:
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 浜ゆ槗ID锛堜富閿級 |
+| orderId | Long | 鎵�灞炶鍗旾D |
+| channel | Enum | 鏀粯娓犻亾 |
+| clientType | Enum | 瀹㈡埛绔被鍨嬶紙NATIVE/ALIPAY_PRECREATE锛� |
+| status | Enum | 浜ゆ槗鐘舵�� |
+| codeOrQr | String | 娓犻亾杩斿洖鐨勪簩缁寸爜鍐呭锛坈ode_url/qr_code锛� |
+| qrBase64 | Text | 鏈嶅姟绔敓鎴愮殑Base64浜岀淮鐮佸浘鐗� |
+| requestParams | Text | 娓犻亾璇锋眰鍙傛暟蹇収 |
+| responseSnapshot | Text | 娓犻亾鍝嶅簲蹇収 |
+| channelTradeNo | String | 娓犻亾浜ゆ槗鍙� |
+| createdAt | DateTime | 鍒涘缓鏃堕棿 |
+| paidAt | DateTime | 鏀粯瀹屾垚鏃堕棿 |
+
+**浜ゆ槗鐘舵�佹灇涓�** (`status`):
+- `PENDING`: 寰呮敮浠�
+- `SUCCEEDED`: 鏀粯鎴愬姛
+- `FAILED`: 鏀粯澶辫触
+- `CANCELED`: 宸插彇娑�
+
+**涓氬姟瑙勫垯**:
+1. 姣忔鍙戣捣鏀粯鏃讹紝鑻ヨ鍗曞凡鏈� PENDING 浜ゆ槗锛屽垯澶嶇敤璇ヤ氦鏄撳苟杩斿洖鍏朵簩缁寸爜
+2. 鑻ユ棤 PENDING 浜ゆ槗锛屽垯鍒涘缓鏂颁氦鏄�
+3. 涓�鏃︿氦鏄撴垚鍔燂紝璁㈠崟鐘舵�佸悓姝ヤ负 SUCCEEDED
+
+### 3. NotifyLog锛堟笭閬撳洖璋冩棩蹇� - 瀹炰綋锛�
+
+**瀛楁**:
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 鏃ュ織ID |
+| channel | Enum | 鏀粯娓犻亾 |
+| notifyIdOrSerial | String | 娓犻亾閫氱煡鍞竴鏍囪瘑 |
+| orderId | Long | 璁㈠崟ID |
+| transactionId | Long | 浜ゆ槗ID |
+| payload | Text | 鍥炶皟鍘熷鎶ユ枃 |
+| verified | Boolean | 绛惧悕楠岃瘉鏄惁閫氳繃 |
+| processed | Boolean | 鏄惁宸插鐞� |
+| result | String | 澶勭悊缁撴灉 |
+| createdAt | DateTime | 鎺ユ敹鏃堕棿 |
+
+**骞傜瓑鎺у埗**:
+- 鍞竴绱㈠紩锛歚channel + notifyIdOrSerial`
+- 閲嶅閫氱煡鐩存帴杩斿洖鎴愬姛搴旂瓟
+
+### 4. BizCallbackLog锛堜笟鍔″洖璋冩棩蹇� - 瀹炰綋锛�
+
+**瀛楁**:
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 鏃ュ織ID |
+| orderId | Long | 璁㈠崟ID |
+| transactionId | Long | 浜ゆ槗ID |
+| callbackUrl | String | 鍥炶皟鍦板潃 |
+| payload | Text | 鍥炶皟璇锋眰浣� |
+| httpStatus | Integer | HTTP 鐘舵�佺爜 |
+| response | Text | 鍝嶅簲鍐呭 |
+| success | Boolean | 鏄惁鎴愬姛 |
+| retryCount | Integer | 閲嶈瘯娆℃暟 |
+| lastRetryAt | DateTime | 鏈�鍚庨噸璇曟椂闂� |
+| createdAt | DateTime | 鍒涘缓鏃堕棿 |
+
+**閲嶈瘯绛栫暐**:
+- 闂撮殧锛�0/1/5/15/60 鍒嗛挓
+- 鏈�澶氶噸璇曪細10娆�
+- 鏀寔绠$悊绔墜宸ラ噸鍙�
+
+### 5. OperationAudit锛堟搷浣滃璁� - 瀹炰綋锛�
+
+**瀛楁**:
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 瀹¤ID |
+| operator | String | 鎿嶄綔浜� |
+| operationType | Enum | 鎿嶄綔绫诲瀷 |
+| orderId | Long | 璁㈠崟ID锛堝彲閫夛級 |
+| transactionId | Long | 浜ゆ槗ID锛堝彲閫夛級 |
+| params | Text | 鎿嶄綔鍙傛暟 |
+| approved | Boolean | 鏄惁閫氳繃锛堣褰曪紝涓嶈蛋瀹℃壒娴佺▼锛� |
+| createdAt | DateTime | 鎿嶄綔鏃堕棿 |
+
+**鎿嶄綔绫诲瀷**:
+- `CLOSE_ORDER`: 鍏抽棴璁㈠崟
+- `RESEND_CALLBACK`: 閲嶅彂涓氬姟鍥炶皟
+- `MANUAL_FIX`: 鎵嬪伐淇瀵硅处宸紓
+- `UPDATE_CHANNEL_ACCOUNT`: 鏇存柊娓犻亾璐﹀彿
+
+### 6. 鍊煎璞�
+
+- **Money**: `{ amountInCents, currency }`
+- **BizOrderId**: 澶栭儴涓氬姟璁㈠崟鍙峰皝瑁�
+- **CallbackUrl**: 涓氬姟鍥炶皟鍦板潃灏佽
+- **ChannelTradeNo**: 娓犻亾浜ゆ槗鍙峰皝瑁�
+
+---
+
+## 鍥涖�佹牳蹇冪敤渚嬩笌娴佺▼
+
+### 1. 鍙戣捣浜岀淮鐮佹敮浠�
+
+**鍏ュ弬**:
+- `bizOrderId`: 澶栭儴涓氬姟璁㈠崟鍙�
+- `amount`: 閲戦锛堝垎锛�
+- `subject`: 璁㈠崟鏍囬
+- `description`: 璁㈠崟鎻忚堪
+- `channel`: 鏀粯娓犻亾锛圵ECHAT/ALIPAY锛�
+- `callbackUrl`: 涓氬姟鍥炶皟鍦板潃
+
+**娴佺▼**:
+```
+1. 鏌ヨ璁㈠崟鏄惁瀛樺湪
+ - 涓嶅瓨鍦� 鈫� 鍒涘缓璁㈠崟锛堢姸鎬� PENDING锛宔xpireAt = now + 2灏忔椂锛�
+ - 瀛樺湪 鈫� 鍔犺浇璁㈠崟
+
+2. 妫�鏌ヨ鍗曟槸鍚︽湁杩涜涓殑浜ゆ槗锛圥ENDING锛�
+ - 鏈� 鈫� 鐩存帴杩斿洖宸叉湁浜ゆ槗鐨勪簩缁寸爜 Base64
+ - 鏃� 鈫� 鍒涘缓鏂颁氦鏄�
+
+3. 璋冪敤娓犻亾涓嬪崟鎺ュ彛
+ 銆愬井淇°��
+ - 鎺ュ彛锛歷2 unifiedorder
+ - trade_type: native
+ - 绛惧悕鏂瑰紡锛歁D5
+ - notify_url: https://api.966120.com/api/pay/notify/wechat
+ - 杩斿洖锛歝ode_url
+
+ 銆愭敮浠樺疂銆�
+ - 鎺ュ彛锛歛lipay.trade.precreate
+ - notify_url: https://api.966120.com/api/pay/notify/alipay
+ - 杩斿洖锛歲r_code
+
+4. 鐢熸垚浜岀淮鐮�
+ - 灏� code_url/qr_code 鐢熸垚 300脳300 PNG 浜岀淮鐮�
+ - 缂栫爜涓� Base64 瀛楃涓�
+ - 淇濆瓨鍒颁氦鏄撹褰曪紙qrBase64锛�
+
+5. 杩斿洖缁欏墠绔�
+ {
+ "orderId": xxx,
+ "transactionId": xxx,
+ "status": "PENDING",
+ "qrBase64": "data:image/png;base64,iVBORw0KG...",
+ "expireAt": "2025-11-23T12:00:00"
+ }
+```
+
+**骞傜瓑鎺у埗**:
+- 鍚屼竴璁㈠崟浠呭厑璁镐竴绗� PENDING 浜ゆ槗瀛樺湪
+- 閲嶅璇锋眰鐩存帴杩斿洖宸叉湁浜ゆ槗鏁版嵁
+
+### 2. 娓犻亾鍥炶皟澶勭悊锛圵ebhook锛�
+
+**寰俊v2鍥炶皟**:
+```
+1. 鎺ユ敹 XML 鎶ユ枃
+2. 楠岃瘉绛惧悕锛圡D5锛�
+3. 瑙f瀽鎶ユ枃锛屾彁鍙栦氦鏄撳彿涓庢敮浠樼粨鏋�
+4. 鏌ヨ璁㈠崟涓庝氦鏄�
+5. 鐘舵�佽浆鎹�
+ - 鎴愬姛 鈫� 浜ゆ槗鐘舵�� SUCCEEDED锛岃鍗曠姸鎬� SUCCEEDED
+ - 澶辫触 鈫� 浜ゆ槗鐘舵�� FAILED
+6. 鎸佷箙鍖栨洿鏂帮紙涔愯閿侊級
+7. 璁板綍 NotifyLog锛堝箓绛夋帶鍒讹級
+8. 瑙﹀彂澶栭儴涓氬姟鍥炶皟
+9. 杩斿洖寰俊瑕佹眰鐨� XML 搴旂瓟
+```
+
+**鏀粯瀹濆洖璋�**:
+```
+1. 鎺ユ敹琛ㄥ崟鍙傛暟
+2. 楠岃瘉绛惧悕锛圧SA2锛�
+3. 瑙f瀽浜ゆ槗鐘舵��
+4. 鍚屾璁㈠崟涓庝氦鏄撶姸鎬�
+5. 璁板綍 NotifyLog
+6. 瑙﹀彂澶栭儴涓氬姟鍥炶皟
+7. 杩斿洖 "success"
+```
+
+**骞傜瓑淇濋殰**:
+- 鍩轰簬 `channel + notifyIdOrSerial` 鍞竴绱㈠紩
+- 閲嶅閫氱煡鐩存帴杩斿洖鎴愬姛搴旂瓟锛屼笉閲嶅澶勭悊
+
+### 3. 澶栭儴涓氬姟鍥炶皟
+
+**瑙﹀彂鏃舵満**: 璁㈠崟鐘舵�佸彉鏇翠负 SUCCEEDED 鍚�
+
+**鍗忚**: HTTP POST JSON
+
+**璇锋眰澶�**:
+- `Content-Type: application/json`
+- `X-Signature`: HMAC-SHA256(payload + nonce + timestamp, callbackSignSecret)
+- `X-Nonce`: 闅忔満瀛楃涓�
+- `X-Timestamp`: 姣鏃堕棿鎴�
+
+**璇锋眰浣�**:
+```json
+{
+ "tradeId": 1234567890,
+ "orderId": 9876543210,
+ "bizOrderId": "BIZ20251123001",
+ "channel": "WECHAT",
+ "amount": 10000,
+ "currency": "CNY",
+ "status": "SUCCEEDED",
+ "channelTradeNo": "4200001234567890",
+ "paidAt": "2025-11-23T10:30:00",
+ "subject": "鍟嗗搧璁㈠崟鏀粯",
+ "description": "璁㈠崟璇︽儏"
+}
+```
+
+**閴存潈鏂瑰紡**:
+- 浣跨敤 HMAC-SHA256 绛惧悕
+- 瀵嗛挜锛氶厤缃枃浠朵腑鐨� `callbackSignSecret`
+- 绛惧悕鍐呭锛歚payload + nonce + timestamp`
+- 澶栭儴绯荤粺浣跨敤鐩稿悓瀵嗛挜楠岃瘉绛惧悕
+
+**閲嶈瘯绛栫暐**:
+- 澶辫触閲嶈瘯闂撮殧锛�0/1/5/15/60 鍒嗛挓
+- 鏈�澶氶噸璇曪細10娆�
+- 鏀寔绠$悊绔墜宸ラ噸鍙�
+
+### 4. 璁㈠崟杩囨湡澶勭悊
+
+**瑙﹀彂鏂瑰紡**: 瀹氭椂浠诲姟锛堝缓璁瘡10鍒嗛挓锛�
+
+**閫昏緫**:
+```
+1. 鏌ヨ鐘舵�佷负 PENDING 涓� expireAt < now 鐨勮鍗�
+2. 灏嗚鍗曠姸鎬佹洿鏂颁负 EXPIRED
+3. 璁板綍鎿嶄綔鏃ュ織
+```
+
+**杩囨湡鏃堕棿**:
+- 浠庤鍗曢娆″垱寤烘椂闂磋捣绠� +2灏忔椂
+- 鍙戣捣鏂颁氦鏄撲笉鍒锋柊杩囨湡鏃堕棿
+
+### 5. 姣忔棩瀵硅处锛堣嚜鍔ㄤ慨澶嶏級
+
+**璋冨害鏃堕棿**: 姣忓ぉ 02:00
+
+**娴佺▼**:
+```
+1. 纭畾瀵硅处鏃ユ湡锛圱-1锛�
+2. 鏌ヨ鏈湴璇ユ棩鎵�鏈夎鍗曚笌浜ゆ槗
+3. 璋冪敤娓犻亾鏌ヨ鎺ュ彛锛堟壒閲�/鍗曠瑪锛�
+ - 寰俊锛歰rderquery
+ - 鏀粯瀹濓細alipay.trade.query
+4. 瀵规瘮鏈湴鐘舵�佷笌娓犻亾鐘舵��
+5. 鑷姩淇
+ - 鏈湴 PENDING锛屾笭閬撳凡鎴愬姛 鈫� 淇涓� SUCCEEDED
+ - 鏈湴 PENDING锛屾笭閬撳凡鍏抽棴 鈫� 淇涓� FAILED
+6. 宸紓璁板綍
+ - 閲戦涓嶄竴鑷�
+ - 娓犻亾鏃犺褰曚絾鏈湴瀛樺湪
+ - 鍏朵粬寮傚父鎯呭喌
+7. 鐢熸垚瀵硅处鎶ュ憡
+ - 鎬昏鍗曟暟
+ - 鎴愬姛鏁�
+ - 澶辫触鏁�
+ - 宸紓鏁�
+ - 鑷姩淇鏁�
+8. 淇濆瓨瀵硅处浠诲姟涓庣粨鏋滄槑缁�
+```
+
+**宸紓澶勭悊**:
+- 鐘舵�佸樊寮傦細鑷姩淇骞惰褰�
+- 閲戦宸紓锛氫粎璁板綍锛屼笉鑷姩淇锛岄渶浜哄伐浠嬪叆
+- 涓ラ噸宸紓锛氬憡璀﹂�氱煡
+
+---
+
+## 浜斻�佹笭閬撻�傞厤涓庡鎴风灏佽
+
+### 1. 寰俊鏀粯 v2锛圢ative锛�
+
+**閰嶇疆椤�** (`application.yml`):
+```yaml
+payment:
+ wechat:
+ appId: wx70f6a7346ee842xx
+ mchId: 1573728xxx
+ mchKey: Xz0ClPK3f5sCeT6SGa1vpVmyUFcbpxxx
+ notifyUrl: https://api.966120.com/api/pay/notify/wechat
+ signType: MD5
+```
+
+**鎺ュ彛灏佽** (`WxPayV2Client`):
+- `createNativeOrder()`: 缁熶竴涓嬪崟锛岃繑鍥� code_url
+- `queryOrder()`: 鏌ヨ璁㈠崟鐘舵��
+- `closeOrder()`: 鍏抽棴璁㈠崟
+- `verifyNotify()`: 楠岃瘉鍥炶皟绛惧悕
+- `buildNotifyResponse()`: 鏋勯�犲簲绛旀姤鏂�
+
+**绛惧悕鏂瑰紡**: MD5
+
+**鍏抽敭娴佺▼**:
+1. 鍙傛暟鎸� ASCII 鎺掑簭鎷兼帴
+2. 杩藉姞 `&key=mchKey`
+3. MD5 鍔犲瘑骞惰浆澶у啓
+
+### 2. 鏀粯瀹濆綋闈粯锛圥recreate锛�
+
+**閰嶇疆椤�** (`application.yml`):
+```yaml
+payment:
+ alipay:
+ appId: 2021xxxxxxxxxxxxx
+ privateKey: MIIEvQIBADANBgkqhki...锛堝晢鎴稲SA2绉侀挜锛�
+ alipayPublicKey: MIIBIjANBgkqhki...锛堟敮浠樺疂RSA2鍏挜锛�
+ serverUrl: https://openapi.alipay.com/gateway.do
+ notifyUrl: https://api.966120.com/api/pay/notify/alipay
+ signType: RSA2
+```
+
+**鎺ュ彛灏佽** (`AlipayF2FClient`):
+- `precreate()`: 褰撻潰浠樹笅鍗曪紝杩斿洖 qr_code
+- `query()`: 鏌ヨ璁㈠崟鐘舵��
+- `close()`: 鍏抽棴璁㈠崟
+- `verifyNotify()`: 楠岃瘉鍥炶皟绛惧悕
+
+**绛惧悕鏂瑰紡**: RSA2
+
+**SDK**: 浣跨敤瀹樻柟 `alipay-sdk-java`
+
+### 3. 娓犻亾瀹㈡埛绔粺涓�鎺ュ彛
+
+```java
+public interface ChannelClient {
+ /**
+ * 鍒涘缓浜岀淮鐮佽鍗�
+ */
+ QrOrderResponse createQrOrder(QrOrderRequest request);
+
+ /**
+ * 鏌ヨ璁㈠崟鐘舵��
+ */
+ OrderQueryResponse queryOrder(String channelTradeNo);
+
+ /**
+ * 鍏抽棴璁㈠崟
+ */
+ void closeOrder(String channelTradeNo);
+
+ /**
+ * 楠岃瘉鍥炶皟绛惧悕
+ */
+ boolean verifyNotify(Map<String, String> params);
+}
+```
+
+**宸ュ巶妯″紡閫夋嫨瀹炵幇**:
+- `WxPayV2Client implements ChannelClient`
+- `AlipayF2FClient implements ChannelClient`
+
+---
+
+## 鍏�佷簩缁寸爜鐢熸垚瑙勮寖
+
+**鎶�鏈柟妗�**: 浣跨敤 ZXing 搴�
+
+**鍙傛暟**:
+- 灏哄锛�300 脳 300
+- 鏍煎紡锛歅NG
+- 绾犻敊绛夌骇锛歁锛堥粯璁わ級
+- 缂栫爜锛歎TF-8
+
+**杈撳嚭鏍煎紡**:
+```
+data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
+```
+
+**瀹炵幇**:
+```java
+// 浼唬鐮�
+BufferedImage qrImage = QRCodeGenerator.generate(codeUrl, 300, 300);
+ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ImageIO.write(qrImage, "PNG", baos);
+String base64 = "data:image/png;base64," + Base64.getEncoder().encodeToString(baos.toByteArray());
+```
+
+---
+
+## 涓冦�佹暟鎹〃璁捐
+
+### 1. pay_order锛堟敮浠樿鍗曡〃锛�
+
+```sql
+CREATE TABLE `pay_order` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '璁㈠崟ID',
+ `biz_order_id` VARCHAR(64) NOT NULL COMMENT '涓氬姟璁㈠崟鍙�',
+ `amount` INT NOT NULL COMMENT '閲戦锛堝垎锛�',
+ `currency` VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '甯佺',
+ `channel` VARCHAR(16) NOT NULL COMMENT '鏀粯娓犻亾',
+ `status` VARCHAR(16) NOT NULL COMMENT '璁㈠崟鐘舵��',
+ `subject` VARCHAR(128) NOT NULL COMMENT '璁㈠崟鏍囬',
+ `description` VARCHAR(512) COMMENT '璁㈠崟鎻忚堪',
+ `callback_url` VARCHAR(512) NOT NULL COMMENT '涓氬姟鍥炶皟鍦板潃',
+ `expire_at` DATETIME NOT NULL COMMENT '杩囨湡鏃堕棿',
+ `latest_transaction_id` BIGINT COMMENT '鏈�鏂颁氦鏄揑D',
+ `channel_trade_no` VARCHAR(64) COMMENT '娓犻亾浜ゆ槗鍙�',
+ `paid_at` DATETIME COMMENT '鏀粯鎴愬姛鏃堕棿',
+ `version` INT NOT NULL DEFAULT 0 COMMENT '涔愯閿佺増鏈彿',
+ `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ INDEX `idx_biz_order_id` (`biz_order_id`),
+ INDEX `idx_channel_trade_no` (`channel_trade_no`),
+ INDEX `idx_status` (`status`),
+ INDEX `idx_expire_at` (`expire_at`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鏀粯璁㈠崟琛�';
+```
+
+### 2. pay_transaction锛堟敮浠樹氦鏄撴祦姘磋〃锛�
+
+```sql
+CREATE TABLE `pay_transaction` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '浜ゆ槗ID',
+ `order_id` BIGINT NOT NULL COMMENT '璁㈠崟ID',
+ `channel` VARCHAR(16) NOT NULL COMMENT '鏀粯娓犻亾',
+ `client_type` VARCHAR(32) NOT NULL COMMENT '瀹㈡埛绔被鍨�',
+ `status` VARCHAR(16) NOT NULL COMMENT '浜ゆ槗鐘舵��',
+ `code_or_qr` VARCHAR(512) COMMENT '浜岀淮鐮佸唴瀹�',
+ `qr_base64` TEXT COMMENT 'Base64浜岀淮鐮佸浘鐗�',
+ `request_params` TEXT COMMENT '璇锋眰鍙傛暟蹇収',
+ `response_snapshot` TEXT COMMENT '鍝嶅簲蹇収',
+ `channel_trade_no` VARCHAR(64) COMMENT '娓犻亾浜ゆ槗鍙�',
+ `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `paid_at` DATETIME COMMENT '鏀粯瀹屾垚鏃堕棿',
+ INDEX `idx_order_id` (`order_id`),
+ INDEX `idx_channel_trade_no` (`channel_trade_no`),
+ INDEX `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鏀粯浜ゆ槗娴佹按琛�';
+```
+
+### 3. notify_log锛堟笭閬撳洖璋冩棩蹇楄〃锛�
+
+```sql
+CREATE TABLE `notify_log` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '鏃ュ織ID',
+ `channel` VARCHAR(16) NOT NULL COMMENT '鏀粯娓犻亾',
+ `notify_id_or_serial` VARCHAR(64) NOT NULL COMMENT '娓犻亾閫氱煡鍞竴鏍囪瘑',
+ `order_id` BIGINT COMMENT '璁㈠崟ID',
+ `transaction_id` BIGINT COMMENT '浜ゆ槗ID',
+ `payload` TEXT NOT NULL COMMENT '鍥炶皟鍘熷鎶ユ枃',
+ `verified` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '绛惧悕楠岃瘉鏄惁閫氳繃',
+ `processed` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '鏄惁宸插鐞�',
+ `result` VARCHAR(128) COMMENT '澶勭悊缁撴灉',
+ `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鎺ユ敹鏃堕棿',
+ UNIQUE KEY `uk_channel_notify` (`channel`, `notify_id_or_serial`),
+ INDEX `idx_order_id` (`order_id`),
+ INDEX `idx_transaction_id` (`transaction_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='娓犻亾鍥炶皟鏃ュ織琛�';
+```
+
+### 4. biz_callback_log锛堜笟鍔″洖璋冩棩蹇楄〃锛�
+
+```sql
+CREATE TABLE `biz_callback_log` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '鏃ュ織ID',
+ `order_id` BIGINT NOT NULL COMMENT '璁㈠崟ID',
+ `transaction_id` BIGINT NOT NULL COMMENT '浜ゆ槗ID',
+ `callback_url` VARCHAR(512) NOT NULL COMMENT '鍥炶皟鍦板潃',
+ `payload` TEXT NOT NULL COMMENT '鍥炶皟璇锋眰浣�',
+ `http_status` INT COMMENT 'HTTP鐘舵�佺爜',
+ `response` TEXT COMMENT '鍝嶅簲鍐呭',
+ `success` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '鏄惁鎴愬姛',
+ `retry_count` INT NOT NULL DEFAULT 0 COMMENT '閲嶈瘯娆℃暟',
+ `last_retry_at` DATETIME COMMENT '鏈�鍚庨噸璇曟椂闂�',
+ `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ INDEX `idx_order_id` (`order_id`),
+ INDEX `idx_success` (`success`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='涓氬姟鍥炶皟鏃ュ織琛�';
+```
+
+### 5. operation_audit锛堟搷浣滃璁¤〃锛�
+
+```sql
+CREATE TABLE `operation_audit` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '瀹¤ID',
+ `operator` VARCHAR(64) NOT NULL COMMENT '鎿嶄綔浜�',
+ `operation_type` VARCHAR(32) NOT NULL COMMENT '鎿嶄綔绫诲瀷',
+ `order_id` BIGINT COMMENT '璁㈠崟ID',
+ `transaction_id` BIGINT COMMENT '浜ゆ槗ID',
+ `params` TEXT COMMENT '鎿嶄綔鍙傛暟',
+ `approved` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '鏄惁閫氳繃',
+ `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鎿嶄綔鏃堕棿',
+ INDEX `idx_operator` (`operator`),
+ INDEX `idx_operation_type` (`operation_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鎿嶄綔瀹¤琛�';
+```
+
+### 6. reconciliation_task锛堝璐︿换鍔¤〃锛�
+
+```sql
+CREATE TABLE `reconciliation_task` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '浠诲姟ID',
+ `task_date` DATE NOT NULL COMMENT '瀵硅处鏃ユ湡',
+ `status` VARCHAR(16) NOT NULL COMMENT '浠诲姟鐘舵��',
+ `total_count` INT NOT NULL DEFAULT 0 COMMENT '鎬昏鍗曟暟',
+ `success_count` INT NOT NULL DEFAULT 0 COMMENT '鎴愬姛鏁�',
+ `failed_count` INT NOT NULL DEFAULT 0 COMMENT '澶辫触鏁�',
+ `diff_count` INT NOT NULL DEFAULT 0 COMMENT '宸紓鏁�',
+ `fixed_count` INT NOT NULL DEFAULT 0 COMMENT '鑷姩淇鏁�',
+ `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `finished_at` DATETIME COMMENT '瀹屾垚鏃堕棿',
+ UNIQUE KEY `uk_task_date` (`task_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='瀵硅处浠诲姟琛�';
+```
+
+### 7. reconciliation_result锛堝璐﹀樊寮傛槑缁嗚〃锛�
+
+```sql
+CREATE TABLE `reconciliation_result` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '鏄庣粏ID',
+ `task_id` BIGINT NOT NULL COMMENT '浠诲姟ID',
+ `order_id` BIGINT NOT NULL COMMENT '璁㈠崟ID',
+ `transaction_id` BIGINT COMMENT '浜ゆ槗ID',
+ `local_status` VARCHAR(16) COMMENT '鏈湴鐘舵��',
+ `channel_status` VARCHAR(16) COMMENT '娓犻亾鐘舵��',
+ `diff_type` VARCHAR(32) NOT NULL COMMENT '宸紓绫诲瀷',
+ `fixed` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '鏄惁宸蹭慨澶�',
+ `note` VARCHAR(512) COMMENT '澶囨敞',
+ `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ INDEX `idx_task_id` (`task_id`),
+ INDEX `idx_order_id` (`order_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='瀵硅处宸紓鏄庣粏琛�';
+```
+
+---
+
+## 鍏�丷ESTful API 鎺ュ彛娓呭崟
+
+### 1. 鍙戣捣寰俊 Native 鏀粯
+```
+POST /api/pay/wechat/native
+```
+**璇锋眰浣�**:
+```json
+{
+ "bizOrderId": "BIZ20251123001",
+ "amount": 10000,
+ "subject": "鍟嗗搧璁㈠崟鏀粯",
+ "description": "璐拱鍟嗗搧A",
+ "callbackUrl": "https://your-business.com/notify"
+}
+```
+**鍝嶅簲**:
+```json
+{
+ "code": 200,
+ "msg": "success",
+ "data": {
+ "orderId": 1234567890,
+ "transactionId": 9876543210,
+ "status": "PENDING",
+ "qrBase64": "data:image/png;base64,iVBORw0KG...",
+ "expireAt": "2025-11-23T12:00:00"
+ }
+}
+```
+
+### 2. 鍙戣捣鏀粯瀹濆綋闈粯
+```
+POST /api/pay/alipay/precreate
+```
+**璇锋眰浣�/鍝嶅簲**: 鍚屼笂
+
+### 3. 寰俊鍥炶皟閫氱煡
+```
+POST /api/pay/notify/wechat
+```
+**璇存槑**: 鎺ユ敹寰俊 XML 鎶ユ枃锛岄獙绛惧悗澶勭悊锛岃繑鍥� XML 搴旂瓟
+
+### 4. 鏀粯瀹濆洖璋冮�氱煡
+```
+POST /api/pay/notify/alipay
+```
+**璇存槑**: 鎺ユ敹琛ㄥ崟鍙傛暟锛岄獙绛惧悗澶勭悊锛岃繑鍥� "success"
+
+### 5. 鏌ヨ璁㈠崟
+```
+GET /api/pay/orders/{orderId}
+```
+**鍝嶅簲**:
+```json
+{
+ "code": 200,
+ "msg": "success",
+ "data": {
+ "orderId": 1234567890,
+ "bizOrderId": "BIZ20251123001",
+ "amount": 10000,
+ "currency": "CNY",
+ "channel": "WECHAT",
+ "status": "SUCCEEDED",
+ "subject": "鍟嗗搧璁㈠崟鏀粯",
+ "channelTradeNo": "4200001234567890",
+ "paidAt": "2025-11-23T10:30:00",
+ "expireAt": "2025-11-23T12:00:00",
+ "createdAt": "2025-11-23T10:00:00"
+ }
+}
+```
+
+### 6. 鏌ヨ鏈�鏂颁氦鏄�
+```
+GET /api/pay/orders/{orderId}/transactions/latest
+```
+**鍝嶅簲**:
+```json
+{
+ "code": 200,
+ "msg": "success",
+ "data": {
+ "transactionId": 9876543210,
+ "orderId": 1234567890,
+ "status": "PENDING",
+ "qrBase64": "data:image/png;base64,iVBORw0KG...",
+ "createdAt": "2025-11-23T10:00:00"
+ }
+}
+```
+
+### 7. 鍏抽棴璁㈠崟
+```
+POST /api/pay/orders/{orderId}/close
+```
+**鍝嶅簲**:
+```json
+{
+ "code": 200,
+ "msg": "璁㈠崟宸插叧闂�"
+}
+```
+
+### 8. 閲嶅彂涓氬姟鍥炶皟
+```
+POST /api/pay/orders/{orderId}/callback/resend
+```
+**鍝嶅簲**:
+```json
+{
+ "code": 200,
+ "msg": "鍥炶皟宸查噸鏂板彂閫�"
+}
+```
+
+### 9. 鏌ヨ瀵硅处浠诲姟鍒楄〃
+```
+GET /api/pay/reconciliation/tasks?date=2025-11-22
+```
+**鍝嶅簲**:
+```json
+{
+ "code": 200,
+ "msg": "success",
+ "data": {
+ "taskId": 12345,
+ "taskDate": "2025-11-22",
+ "status": "FINISHED",
+ "totalCount": 1000,
+ "successCount": 980,
+ "failedCount": 15,
+ "diffCount": 5,
+ "fixedCount": 3,
+ "createdAt": "2025-11-23T02:00:00",
+ "finishedAt": "2025-11-23T02:15:00"
+ }
+}
+```
+
+### 10. 鏌ヨ瀵硅处宸紓鏄庣粏
+```
+GET /api/pay/reconciliation/results?taskId=12345
+```
+**鍝嶅簲**:
+```json
+{
+ "code": 200,
+ "msg": "success",
+ "data": [
+ {
+ "orderId": 1234567890,
+ "transactionId": 9876543210,
+ "localStatus": "PENDING",
+ "channelStatus": "SUCCEEDED",
+ "diffType": "STATUS_DIFF",
+ "fixed": true,
+ "note": "宸茶嚜鍔ㄤ慨澶�"
+ }
+ ]
+}
+```
+
+---
+
+## 涔濄�侀厤缃枃浠剁ず渚�
+
+**application.yml**:
+```yaml
+spring:
+ application:
+ name: dryad-payment
+
+# 鏀粯閰嶇疆
+payment:
+ # 寰俊鏀粯閰嶇疆
+ wechat:
+ appId: wx70f6a7346ee842xx
+ mchId: 1573728xxx
+ mchKey: Xz0ClPK3f5sCeT6SGa1vpVmyUFcbpxxx
+ notifyUrl: https://api.966120.com/api/pay/notify/wechat
+ signType: MD5
+
+ # 鏀粯瀹濋厤缃�
+ alipay:
+ appId: 2021xxxxxxxxxxxxx
+ privateKey: MIIEvQIBADANBgkqhki...
+ alipayPublicKey: MIIBIjANBgkqhki...
+ serverUrl: https://openapi.alipay.com/gateway.do
+ notifyUrl: https://api.966120.com/api/pay/notify/alipay
+ signType: RSA2
+
+ # 涓氬姟鍥炶皟閰嶇疆
+ business:
+ callbackSignSecret: your-hmac-secret-key-here
+ callbackRetryMaxCount: 10
+ callbackRetryIntervals: 0,1,5,15,60 # 鍒嗛挓
+
+ # 浜岀淮鐮侀厤缃�
+ qrcode:
+ size: 300
+ format: PNG
+
+ # 瀵硅处閰嶇疆
+ reconciliation:
+ enabled: true
+ cron: "0 0 2 * * ?" # 姣忓ぉ鍑屾櫒2鐐�
+```
+
+---
+
+## 鍗併�佺鐞嗙椤甸潰鍔熻兘娓呭崟
+
+### 1. 鏀粯璁㈠崟绠$悊
+**璺緞**: `/payment/orders`
+
+**鍔熻兘**:
+- 鍒楄〃灞曠ず锛堟敮鎸佸垎椤碉級
+- 鏉′欢妫�绱細涓氬姟璁㈠崟鍙枫�佹笭閬撱�佺姸鎬併�佹椂闂磋寖鍥�
+- 璇︽儏鏌ョ湅锛氳鍗曚俊鎭�佷氦鏄撹褰曘�佸洖璋冩棩蹇�
+- 鎿嶄綔锛氭墜宸ュ叧闂鍗�
+
+### 2. 浜ゆ槗娴佹按绠$悊
+**璺緞**: `/payment/transactions`
+
+**鍔熻兘**:
+- 鍒楄〃灞曠ず锛堟敮鎸佸垎椤碉級
+- 鏌ョ湅浜岀淮鐮侊紙Base64鍥剧墖锛�
+- 鏌ョ湅娓犻亾璇锋眰/鍝嶅簲蹇収
+- 鎸夎鍗曡繃婊�
+
+### 3. 娓犻亾鍥炶皟鏃ュ織
+**璺緞**: `/payment/notify-logs`
+
+**鍔熻兘**:
+- 鍒楄〃灞曠ず锛堟敮鎸佸垎椤碉級
+- 鏌ョ湅鍘熷鎶ユ枃
+- 楠岀缁撴灉灞曠ず
+- 澶勭悊鐘舵�佷笌缁撴灉
+
+### 4. 涓氬姟鍥炶皟鏃ュ織
+**璺緞**: `/payment/callback-logs`
+
+**鍔熻兘**:
+- 鍒楄〃灞曠ず锛堟敮鎸佸垎椤碉級
+- 鏌ョ湅鍥炶皟璇锋眰/鍝嶅簲
+- 閲嶈瘯娆℃暟涓庣姸鎬�
+- 鎵嬪伐閲嶅彂鍥炶皟
+
+### 5. 瀵硅处绠$悊
+**璺緞**: `/payment/reconciliation`
+
+**鍔熻兘**:
+- 瀵硅处浠诲姟鍒楄〃锛堟寜鏃ユ湡锛�
+- 鏌ョ湅瀵硅处缁熻
+- 宸紓鏄庣粏鍒楄〃
+- 鑷姩淇璁板綍鏌ョ湅
+
+### 6. 鎿嶄綔瀹¤
+**璺緞**: `/payment/audit`
+
+**鍔熻兘**:
+- 瀹¤鏃ュ織鍒楄〃锛堟敮鎸佸垎椤碉級
+- 鎸夋搷浣滀汉銆佹搷浣滅被鍨嬭繃婊�
+- 鏌ョ湅鎿嶄綔鍙傛暟涓庢椂闂�
+
+**璇存槑**: 涓嶆彁渚涘鍑哄姛鑳斤紝鎵�鏈夋暟鎹粎鍦ㄧ嚎鏌ョ湅
+
+---
+
+## 鍗佷竴銆佸畨鍏ㄤ笌鍚堣
+
+### 1. 瀵嗛挜绠$悊
+- 閰嶇疆鏂囦欢鏉冮檺鍙楁帶锛堜粎鏈嶅姟鍣ㄥ彲璇伙級
+- 鏃ュ織鑴辨晱锛堝瘑閽ャ�佽瘉涔﹀唴瀹逛笉杈撳嚭锛�
+- 瀹氭湡鏇存崲瀵嗛挜鏈哄埗锛堥鐣欐帴鍙o級
+
+### 2. 绛惧悕楠岃瘉
+- 鎵�鏈夋笭閬撳洖璋冨繀椤婚獙绛�
+- 澶栭儴涓氬姟鍥炶皟閲囩敤 HMAC-SHA256
+- 绛惧悕澶辫触璁板綍鍛婅
+
+### 3. 骞傜瓑涓庨槻閲�
+- 娓犻亾鍥炶皟锛歯otify 鍞竴閿�
+- 鍙戣捣鏀粯锛氳鍗曠骇鍒帶鍒�
+- 涓氬姟鍥炶皟锛氶噸璇曟満鍒� + 鏃ュ織璁板綍
+
+### 4. 鏁版嵁瀹夊叏
+- 鏁忔劅瀛楁鍔犲瘑瀛樺偍锛堝鏈夐渶瑕侊級
+- 浼犺緭灞� HTTPS 寮哄埗
+- 鍥炶皟 URL 鐧藉悕鍗曟牎楠岋紙鍙�夛級
+
+### 5. 鎿嶄綔瀹¤
+- 鎵�鏈夊叧閿搷浣滆褰曞璁℃棩蹇�
+- 鎿嶄綔浜恒�佹椂闂淬�佸弬鏁板畬鏁磋褰�
+- 鏀寔鍥炴函涓庤拷璐�
+
+---
+
+## 鍗佷簩銆佺洃鎺т笌鍛婅
+
+### 1. 鍏抽敭鎸囨爣
+- 璁㈠崟鏀粯鎴愬姛鐜�
+- 娓犻亾鍥炶皟楠岀澶辫触鐜�
+- 涓氬姟鍥炶皟鎴愬姛鐜�
+- 瀵硅处宸紓鏁伴噺
+- 璁㈠崟杩囨湡鐜�
+
+### 2. 鍛婅瑙勫垯
+- 鍥炶皟楠岀澶辫触 > 闃堝��
+- 涓氬姟鍥炶皟澶辫触鐜� > 闃堝��
+- 瀵硅处宸紓鏁� > 闃堝��
+- 娓犻亾鎺ュ彛寮傚父锛堣秴鏃�/閿欒鐮侊級
+
+### 3. 鏃ュ織
+- 缁熶竴 traceId 璐┛鍏ㄩ摼璺�
+- 鍏抽敭鑺傜偣鏃ュ織杈撳嚭
+- 閿欒鍫嗘爤瀹屾暣璁板綍
+
+---
+
+## 鍗佷笁銆佸悗缁墿灞曡鍒�
+
+### 1. 閫�娆惧姛鑳�
+- RefundOrder 鑱氬悎鏍�
+- 閮ㄥ垎閫�娆炬敮鎸�
+- 閫�娆惧璐�
+
+### 2. 鍒嗚处鍔熻兘
+- 鏀寔寰俊/鏀粯瀹濆垎璐�
+- 鍒嗚处鎺ユ敹鏂圭鐞�
+- 鍒嗚处鏄庣粏涓庡璐�
+
+### 3. 澶氬晢鎴锋敮鎸�
+- ChannelAccount 瀹炰綋婵�娲�
+- 鎸夊晢鎴风淮搴︾鐞嗗瘑閽�
+- 澶氱鎴烽殧绂�
+
+### 4. 鏇村鏀粯鏂瑰紡
+- 寰俊 H5/APP/灏忕▼搴�
+- 鏀粯瀹� APP/WAP
+- 閾惰鍗″揩鎹锋敮浠�
+
+### 5. 瀵硅处浼樺寲
+- 鏀寔涓嬭浇娓犻亾瀵硅处鏂囦欢
+- 鑷姩瑙f瀽涓庢瘮瀵�
+- 宸紓鑷姩淇绛栫暐鍗囩骇
+
+---
+
+## 鍗佸洓銆佸紑鍙戜笌浜や粯璁″垝
+
+### 闃舵涓�锛氭牳蹇冩敮浠樺姛鑳斤紙棰勮 2 鍛級
+- 棰嗗煙妯″瀷涓庢暟鎹簱琛�
+- 寰俊 Native 鏀粯
+- 鏀粯瀹濆綋闈粯
+- 娓犻亾鍥炶皟澶勭悊
+- 浜岀淮鐮佺敓鎴愪笌 Base64 杩斿洖
+
+### 闃舵浜岋細涓氬姟鍥炶皟涓庡璁★紙棰勮 1 鍛級
+- 澶栭儴涓氬姟鍥炶皟閫昏緫
+- HMAC 閴存潈瀹炵幇
+- 閲嶈瘯鏈哄埗
+- 鎿嶄綔瀹¤璁板綍
+
+### 闃舵涓夛細瀵硅处涓庣鐞嗙锛堥璁� 1.5 鍛級
+- 姣忔棩瀵硅处浠诲姟
+- 鑷姩淇閫昏緫
+- 绠$悊绔〉闈㈠紑鍙�
+- 鏉冮檺鑿滃崟闆嗘垚
+
+### 闃舵鍥涳細娴嬭瘯涓庝紭鍖栵紙棰勮 1 鍛級
+- 鍗曞厓娴嬭瘯
+- 闆嗘垚娴嬭瘯锛堟矙绠辩幆澧冿級
+- 鎬ц兘娴嬭瘯涓庝紭鍖�
+- 瀹夊叏娴嬭瘯
+
+---
+
+## 鍗佷簲銆佹妧鏈闄╀笌搴斿
+
+### 1. 骞跺彂鎺у埗
+**椋庨櫓**: 鍚屼竴璁㈠崟骞跺彂鍒涘缓澶氱瑪浜ゆ槗
+**搴斿**: 璁㈠崟绾у埆涔愯閿� + 鏁版嵁搴撳敮涓�绾︽潫
+
+### 2. 鍥炶皟涓㈠け
+**椋庨櫓**: 娓犻亾鍥炶皟鏈垚鍔熼�佽揪
+**搴斿**: 姣忔棩瀵硅处琛ュ伩 + 涓诲姩鏌ヨ鏈哄埗
+
+### 3. 涓氬姟鍥炶皟澶辫触
+**椋庨櫓**: 澶栭儴绯荤粺涓嶅彲鐢ㄥ鑷村洖璋冨け璐�
+**搴斿**: 閲嶈瘯鏈哄埗 + 鎵嬪伐閲嶅彂
+
+### 4. 瀵硅处宸紓
+**椋庨櫓**: 鏈湴涓庢笭閬撶姸鎬佷笉涓�鑷�
+**搴斿**: 鑷姩淇 + 宸紓鍛婅 + 浜哄伐浠嬪叆
+
+### 5. 瀵嗛挜娉勯湶
+**椋庨櫓**: 閰嶇疆鏂囦欢娉勯湶瀵艰嚧瀵嗛挜鏆撮湶
+**搴斿**: 鏂囦欢鏉冮檺鎺у埗 + 鏃ュ織鑴辨晱 + 瀹氭湡鏇存崲
+
+---
+
+## 鍗佸叚銆佹�荤粨
+
+鏈璁℃柟妗堝熀浜� DDD 鏋舵瀯锛屽厖鍒嗚�冭檻浜嗘敮浠樹笟鍔$殑澶嶆潅鎬т笌绋冲仴鎬ч渶姹傦細
+
+1. **娓呮櫚鐨勯鍩熸ā鍨�**: 鑱氬悎鏍广�佸疄浣撱�佸�煎璞¤亴璐f槑纭�
+2. **涓ユ牸鐨勭姸鎬佹帶鍒�**: 璁㈠崟涓庝氦鏄撶姸鎬佹満銆佽繃鏈熸満鍒�
+3. **鍙潬鐨勫洖璋冩満鍒�**: 骞傜瓑銆侀噸璇曘�侀壌鏉�
+4. **鑷姩鍖栧璐�**: 姣忔棩淇銆佸樊寮傚憡璀�
+5. **瀹屾暣鐨勫璁¤拷婧�**: 鎿嶄綔鏃ュ織銆佹笭閬撴棩蹇椼�佷笟鍔℃棩蹇�
+6. **鍙墿灞曟灦鏋�**: 棰勭暀閫�娆俱�佸垎璐︺�佸鍟嗘埛绛夋墿灞曠偣
+
+璇ユ柟妗堝凡瀵归綈锛�
+- 鉁� Java 1.8 + Spring Boot 2.5.15 + ruoyi-framework
+- 鉁� 寰俊鏀粯 v2 缁熶竴涓嬪崟锛圡D5绛惧悕锛�
+- 鉁� 鏀粯瀹濆綋闈粯锛圧SA2绛惧悕锛�
+- 鉁� 鏈嶅姟绔繑鍥� 300脳300 Base64 浜岀淮鐮�
+- 鉁� 澶栭儴涓氬姟鍥炶皟 HMAC 閴存潈
+- 鉁� 鍚屼竴璁㈠崟浠呬竴绗旇繘琛屼腑浜ゆ槗
+- 鉁� 璁㈠崟 2 灏忔椂鑷姩杩囨湡
+- 鉁� 姣忔棩 2 鐐硅嚜鍔ㄥ璐︿慨澶�
+- 鉁� 鍗曞晢鎴烽厤缃枃浠跺姞杞�
+- 鉁� 绠$悊绔〉闈紙涓嶅鍑猴級
+
+**鏂规宸茬‘璁わ紝鍙繘鍏ヤ唬鐮佸疄鐜伴樁娈点��**
diff --git a/dryad-payment/pom.xml b/dryad-payment/pom.xml
new file mode 100644
index 0000000..165e949
--- /dev/null
+++ b/dryad-payment/pom.xml
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>com.ruoyi</groupId>
+ <artifactId>dryad-payment</artifactId>
+ <version>1.0.0</version>
+ <packaging>jar</packaging>
+
+ <name>dryad-payment</name>
+ <description>鏀粯妯″潡鏈嶅姟锛堝彲鐙珛杩愯鎴栦綔涓虹粍浠堕泦鎴愶級</description>
+
+ <properties>
+ <java.version>1.8</java.version>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <spring-boot.version>2.5.15</spring-boot.version>
+ <mybatis-plus.version>3.4.3.4</mybatis-plus.version>
+ <druid.version>1.2.8</druid.version>
+ <fastjson.version>1.2.83</fastjson.version>
+ <commons-lang3.version>3.12.0</commons-lang3.version>
+ <httpclient.version>4.5.13</httpclient.version>
+ <alipay-sdk.version>4.22.110.ALL</alipay-sdk.version>
+ <zxing.version>3.4.1</zxing.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-dependencies</artifactId>
+ <version>${spring-boot.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <!-- Spring Boot Starter Web -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+
+ <!-- Spring Boot Starter AOP -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-aop</artifactId>
+ </dependency>
+
+ <!-- Spring Boot Starter Validation -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-validation</artifactId>
+ </dependency>
+
+ <!-- MyBatis -->
+ <dependency>
+ <groupId>org.mybatis.spring.boot</groupId>
+ <artifactId>mybatis-spring-boot-starter</artifactId>
+ <version>2.2.2</version>
+ </dependency>
+
+ <!-- Druid 鏁版嵁搴撹繛鎺ユ睜 -->
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>druid-spring-boot-starter</artifactId>
+ <version>${druid.version}</version>
+ </dependency>
+
+ <!-- MySQL Driver -->
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+
+ <!-- FastJSON -->
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>fastjson</artifactId>
+ <version>${fastjson.version}</version>
+ </dependency>
+
+ <!-- Commons Lang3 -->
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>${commons-lang3.version}</version>
+ </dependency>
+
+ <!-- HttpClient -->
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>${httpclient.version}</version>
+ </dependency>
+
+ <!-- HttpClient Mime (鏀寔 multipart/form-data) -->
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpmime</artifactId>
+ <version>${httpclient.version}</version>
+ </dependency>
+
+ <!-- Commons Codec -->
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+
+ <!-- 鏀粯瀹漇DK -->
+ <dependency>
+ <groupId>com.alipay.sdk</groupId>
+ <artifactId>alipay-sdk-java</artifactId>
+ <version>${alipay-sdk.version}</version>
+ </dependency>
+
+ <!-- ZXing 浜岀淮鐮佺敓鎴� -->
+ <dependency>
+ <groupId>com.google.zxing</groupId>
+ <artifactId>core</artifactId>
+ <version>${zxing.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.zxing</groupId>
+ <artifactId>javase</artifactId>
+ <version>${zxing.version}</version>
+ </dependency>
+
+ <!-- Lombok -->
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <optional>true</optional>
+ </dependency>
+
+ <!-- Spring Boot Configuration Processor -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-configuration-processor</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>com.ruoyi</groupId>
+ <artifactId>ruoyi-common</artifactId>
+ <version>3.9.0</version>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- 鏀寔鎵撳寘鎴愬彲鎵цjar锛堢嫭绔嬭繍琛岋級 -->
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${spring-boot.version}</version>
+ <configuration>
+ <!-- 鍙�夛細涓嶅皢渚濊禆鎵撳寘鍒癹ar涓紝浣滀负缁勪欢鏃舵洿杞婚噺 -->
+ <skip>false</skip>
+ </configuration>
+ <executions>
+ <execution>
+ <id>repackage</id>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ <configuration>
+ <!-- 鐢熸垚鍙墽琛宩ar -->
+ <classifier>exec</classifier>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- 鐢熸垚sources jar渚涘叾浠栭」鐩紩鐢� -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>3.2.1</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/PaymentApplication.java b/dryad-payment/src/main/java/com/ruoyi/payment/PaymentApplication.java
new file mode 100644
index 0000000..5bf77a6
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/PaymentApplication.java
@@ -0,0 +1,29 @@
+package com.ruoyi.payment;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+/**
+ * 鏀粯妯″潡鍚姩绫�
+ *
+ * 浣跨敤鏂瑰紡锛�
+ * 1. 鐙珛杩愯锛氱洿鎺ヨ繍琛屾绫荤殑 main 鏂规硶
+ * 2. 浣滀负缁勪欢闆嗘垚锛�
+ * - 鍦ㄤ富椤圭洰 pom.xml 涓坊鍔犱緷璧�
+ * - Spring Boot 浼氳嚜鍔ㄦ壂鎻� PaymentAutoConfiguration
+ * - 鏃犻渶棰濆閰嶇疆锛屽嵆鍙娇鐢ㄦ敮浠樺姛鑳�
+ *
+ * @author ruoyi
+ */
+@SpringBootApplication
+@EnableScheduling
+@MapperScan("com.ruoyi.payment.infrastructure.persistence.mapper")
+public class PaymentApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(PaymentApplication.class, args);
+ System.out.println("(鈾モ棤鈥库棤)銉庯豢 鏀粯妯″潡鍚姩鎴愬姛 醿�(麓凇`醿�)锘�");
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/application/service/BizCallbackService.java b/dryad-payment/src/main/java/com/ruoyi/payment/application/service/BizCallbackService.java
new file mode 100644
index 0000000..5e2d931
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/application/service/BizCallbackService.java
@@ -0,0 +1,247 @@
+package com.ruoyi.payment.application.service;
+
+import com.alibaba.fastjson.JSON;
+import com.ruoyi.payment.domain.event.PaymentSuccessEvent;
+import com.ruoyi.payment.domain.model.BizCallbackLog;
+import com.ruoyi.payment.domain.model.PaymentOrder;
+import com.ruoyi.payment.domain.model.PaymentTransaction;
+import com.ruoyi.payment.infrastructure.config.BusinessCallbackConfig;
+import com.ruoyi.payment.infrastructure.persistence.mapper.BizCallbackLogMapper;
+import com.ruoyi.payment.infrastructure.util.SignUtil;
+import com.ruoyi.payment.infrastructure.util.SnowflakeIdGenerator;
+import com.ruoyi.payment.interfaces.callback.PaymentCallback;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * 涓氬姟鍥炶皟鏈嶅姟
+ *
+ * 鏀寔涓夌鍥炶皟鏂瑰紡锛�
+ * 1. Spring浜嬩欢锛氬彂甯� PaymentSuccessEvent 浜嬩欢锛堟帹鑽愮敤浜巎ar鍖呴泦鎴愶級
+ * 2. 鎺ュ彛鍥炶皟锛氳皟鐢ㄦ墍鏈夊疄鐜颁簡 PaymentCallback 鎺ュ彛鐨凚ean锛堟帹鑽愮敤浜巎ar鍖呴泦鎴愶級
+ * 3. HTTP鍥炶皟锛氬悜鎸囧畾URL鍙戦�丳OST璇锋眰锛堢敤浜庣嫭绔嬮儴缃叉垨璺ㄦ湇鍔¤皟鐢級
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@Service
+public class BizCallbackService {
+
+ @Autowired
+ private BizCallbackLogMapper bizCallbackLogMapper;
+
+ @Autowired
+ private BusinessCallbackConfig businessCallbackConfig;
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ /**
+ * 瑙﹀彂涓氬姟鍥炶皟锛堝紓姝ワ級
+ *
+ * 浼樺厛绾э細
+ * 1. 鍙戝竷Spring浜嬩欢锛堝悓姝ワ紝鍦ㄥ悓涓�浜嬪姟涓級
+ * 2. 璋冪敤PaymentCallback鎺ュ彛瀹炵幇锛堝悓姝ワ紝鍦ㄥ悓涓�浜嬪姟涓級
+ * 3. HTTP鍥炶皟锛堝紓姝ワ紝鏀寔閲嶈瘯锛�
+ */
+ @Async
+ public void triggerCallback(PaymentOrder order, PaymentTransaction transaction) {
+ log.info("瑙﹀彂涓氬姟鍥炶皟锛岃鍗旾D: {}, bizOrderId: {}", order.getId(), order.getBizOrderId());
+
+ // 鏂瑰紡1: 鍙戝竷Spring浜嬩欢锛坖ar鍖呴泦鎴愭椂锛屼富椤圭洰鍙互鐩戝惉姝や簨浠讹級
+ publishPaymentEvent(order, transaction);
+
+ // 鏂瑰紡2: 璋冪敤PaymentCallback鎺ュ彛瀹炵幇锛坖ar鍖呴泦鎴愭椂锛屼富椤圭洰瀹炵幇姝ゆ帴鍙o級
+ invokeCallbackInterface(order, transaction);
+
+ // 鏂瑰紡3: HTTP鍥炶皟锛堢嫭绔嬮儴缃叉椂浣跨敤锛岄渶瑕侀厤缃甤allbackUrl锛�
+ if (StringUtils.hasText(order.getCallbackUrl())) {
+ executeHttpCallback(order, transaction);
+ } else {
+ log.info("鏈厤缃瓾TTP鍥炶皟URL锛岃烦杩嘓TTP鍥炶皟");
+ }
+ }
+
+ /**
+ * 鍙戝竷鏀粯鎴愬姛浜嬩欢
+ */
+ private void publishPaymentEvent(PaymentOrder order, PaymentTransaction transaction) {
+ try {
+ PaymentSuccessEvent event = new PaymentSuccessEvent(this, order, transaction);
+ applicationContext.publishEvent(event);
+ log.info("鍙戝竷鏀粯鎴愬姛浜嬩欢锛岃鍗旾D: {}", order.getId());
+ } catch (Exception e) {
+ log.error("鍙戝竷鏀粯浜嬩欢澶辫触", e);
+ }
+ }
+
+ /**
+ * 璋冪敤PaymentCallback鎺ュ彛瀹炵幇
+ */
+ private void invokeCallbackInterface(PaymentOrder order, PaymentTransaction transaction) {
+ try {
+ Map<String, PaymentCallback> callbacks = applicationContext.getBeansOfType(PaymentCallback.class);
+ if (callbacks.isEmpty()) {
+ log.info("鏈壘鍒癙aymentCallback鎺ュ彛瀹炵幇锛岃烦杩囨帴鍙e洖璋�");
+ return;
+ }
+
+ for (Map.Entry<String, PaymentCallback> entry : callbacks.entrySet()) {
+ try {
+ log.info("璋冪敤PaymentCallback: {}", entry.getKey());
+ entry.getValue().onPaymentSuccess(order, transaction);
+ } catch (Exception e) {
+ log.error("璋冪敤PaymentCallback澶辫触: {}", entry.getKey(), e);
+ }
+ }
+ } catch (Exception e) {
+ log.error("璋冪敤PaymentCallback鎺ュ彛澶辫触", e);
+ }
+ }
+
+ /**
+ * 鎵цHTTP鍥炶皟
+ */
+ private void executeHttpCallback(PaymentOrder order, PaymentTransaction transaction) {
+ log.info("鎵цHTTP鍥炶皟锛岃鍗旾D: {}, 鍥炶皟URL: {}", order.getId(), order.getCallbackUrl());
+
+ // 鏋勯�犲洖璋冨弬鏁�
+ Map<String, Object> callbackData = new HashMap<>();
+ callbackData.put("tradeId", transaction.getId());
+ callbackData.put("orderId", order.getId());
+ callbackData.put("bizOrderId", order.getBizOrderId());
+ callbackData.put("channel", order.getChannel());
+ callbackData.put("amount", order.getAmount());
+ callbackData.put("currency", order.getCurrency());
+ callbackData.put("status", order.getStatus());
+ callbackData.put("channelTradeNo", order.getChannelTradeNo());
+ callbackData.put("paidAt", order.getPaidAt() != null ? order.getPaidAt().toString() : null);
+ callbackData.put("subject", order.getSubject());
+ callbackData.put("description", order.getDescription());
+
+ String payload = JSON.toJSONString(callbackData);
+
+ // 鐢熸垚绛惧悕
+ String nonce = UUID.randomUUID().toString().replace("-", "");
+ String timestamp = String.valueOf(System.currentTimeMillis());
+ String signData = payload + nonce + timestamp;
+ String signature = SignUtil.hmacSha256(signData, businessCallbackConfig.getCallbackSignSecret());
+
+ // 璁板綍鍥炶皟鏃ュ織
+ BizCallbackLog callbackLog = new BizCallbackLog();
+ callbackLog.setId(SnowflakeIdGenerator.generateId());
+ callbackLog.setOrderId(order.getId());
+ callbackLog.setTransactionId(transaction.getId());
+ callbackLog.setCallbackUrl(order.getCallbackUrl());
+ callbackLog.setPayload(payload);
+ callbackLog.setSuccess(false);
+ callbackLog.setRetryCount(0);
+ callbackLog.setCreatedAt(LocalDateTime.now());
+ bizCallbackLogMapper.insert(callbackLog);
+
+ // 鎵цHTTP鍥炶皟
+ executeHttpRequest(callbackLog, payload, signature, nonce, timestamp, 0);
+ }
+
+ /**
+ * 鎵цHTTP鍥炶皟璇锋眰
+ */
+ private void executeHttpRequest(BizCallbackLog callbackLog, String payload, String signature,
+ String nonce, String timestamp, int retryCount) {
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(callbackLog.getCallbackUrl());
+
+ // 璁剧疆璇锋眰澶�
+ httpPost.setHeader("Content-Type", "application/json");
+ httpPost.setHeader("X-Signature", signature);
+ httpPost.setHeader("X-Nonce", nonce);
+ httpPost.setHeader("X-Timestamp", timestamp);
+
+ // 璁剧疆璇锋眰浣�
+ httpPost.setEntity(new StringEntity(payload, "UTF-8"));
+
+ // 鍙戦�佽姹�
+ try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+ int statusCode = response.getStatusLine().getStatusCode();
+ String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
+
+ log.info("涓氬姟鍥炶皟鍝嶅簲锛岀姸鎬佺爜: {}, 鍝嶅簲: {}", statusCode, responseBody);
+
+ // 鏇存柊鍥炶皟鏃ュ織
+ callbackLog.setHttpStatus(statusCode);
+ callbackLog.setResponse(responseBody);
+ callbackLog.setRetryCount(retryCount);
+ callbackLog.setLastRetryAt(LocalDateTime.now());
+
+ if (statusCode >= 200 && statusCode < 300) {
+ callbackLog.setSuccess(true);
+ bizCallbackLogMapper.update(callbackLog);
+ log.info("涓氬姟鍥炶皟鎴愬姛锛岃鍗旾D: {}", callbackLog.getOrderId());
+ } else {
+ callbackLog.setSuccess(false);
+ bizCallbackLogMapper.update(callbackLog);
+
+ // 閲嶈瘯閫昏緫
+ scheduleRetry(callbackLog, payload, signature, nonce, timestamp, retryCount);
+ }
+ }
+ } catch (Exception e) {
+ log.error("涓氬姟鍥炶皟寮傚父锛岃鍗旾D: {}", callbackLog.getOrderId(), e);
+
+ // 鏇存柊澶辫触璁板綍
+ callbackLog.setSuccess(false);
+ callbackLog.setResponse("寮傚父: " + e.getMessage());
+ callbackLog.setRetryCount(retryCount);
+ callbackLog.setLastRetryAt(LocalDateTime.now());
+ bizCallbackLogMapper.update(callbackLog);
+
+ // 閲嶈瘯閫昏緫
+ scheduleRetry(callbackLog, payload, signature, nonce, timestamp, retryCount);
+ }
+ }
+
+ /**
+ * 瀹夋帓閲嶈瘯
+ */
+ private void scheduleRetry(BizCallbackLog callbackLog, String payload, String signature,
+ String nonce, String timestamp, int retryCount) {
+ int maxRetry = businessCallbackConfig.getCallbackRetryMaxCount();
+ if (retryCount >= maxRetry) {
+ log.warn("涓氬姟鍥炶皟閲嶈瘯娆℃暟宸茶揪涓婇檺锛岃鍗旾D: {}", callbackLog.getOrderId());
+ return;
+ }
+
+ int[] intervals = businessCallbackConfig.getRetryIntervalsArray();
+ int nextRetry = retryCount + 1;
+ int delayMinutes = nextRetry < intervals.length ? intervals[nextRetry] : intervals[intervals.length - 1];
+
+ log.info("瀹夋帓涓氬姟鍥炶皟閲嶈瘯锛岃鍗旾D: {}, 绗瑊}娆¢噸璇曪紝寤惰繜{}鍒嗛挓",
+ callbackLog.getOrderId(), nextRetry, delayMinutes);
+
+ // TODO: 浣跨敤瀹氭椂浠诲姟鎴栧欢杩熼槦鍒楀疄鐜伴噸璇�
+ // 杩欓噷绠�鍖栧鐞嗭紝瀹為檯搴斾娇鐢� @Scheduled 鎴栨秷鎭槦鍒�
+ }
+
+ /**
+ * 鎵嬪伐閲嶅彂鍥炶皟
+ */
+ public void resendCallback(Long orderId) {
+ // TODO: 瀹炵幇鎵嬪伐閲嶅彂閫昏緫
+ log.info("鎵嬪伐閲嶅彂涓氬姟鍥炶皟锛岃鍗旾D: {}", orderId);
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/application/service/PaymentNotifyService.java b/dryad-payment/src/main/java/com/ruoyi/payment/application/service/PaymentNotifyService.java
new file mode 100644
index 0000000..c4a8ff4
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/application/service/PaymentNotifyService.java
@@ -0,0 +1,218 @@
+package com.ruoyi.payment.application.service;
+
+import com.alibaba.fastjson.JSON;
+import com.ruoyi.payment.domain.model.NotifyLog;
+import com.ruoyi.payment.domain.model.PaymentOrder;
+import com.ruoyi.payment.domain.model.PaymentTransaction;
+import com.ruoyi.payment.infrastructure.persistence.mapper.NotifyLogMapper;
+import com.ruoyi.payment.infrastructure.persistence.mapper.PaymentOrderMapper;
+import com.ruoyi.payment.infrastructure.persistence.mapper.PaymentTransactionMapper;
+import com.ruoyi.payment.infrastructure.util.SnowflakeIdGenerator;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.Map;
+
+/**
+ * 鏀粯鍥炶皟澶勭悊鏈嶅姟
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@Service
+public class PaymentNotifyService {
+
+ @Autowired
+ private PaymentOrderMapper paymentOrderMapper;
+
+ @Autowired
+ private PaymentTransactionMapper paymentTransactionMapper;
+
+ @Autowired
+ private NotifyLogMapper notifyLogMapper;
+
+ @Autowired
+ private BizCallbackService bizCallbackService;
+
+ /**
+ * 澶勭悊寰俊鏀粯鍥炶皟
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public void processWechatNotify(Map<String, String> params, String rawXml) {
+ String outTradeNo = params.get("out_trade_no"); // 鍟嗘埛璁㈠崟鍙�
+ String transactionId = params.get("transaction_id"); // 寰俊浜ゆ槗鍙�
+ String resultCode = params.get("result_code");
+ String returnCode = params.get("return_code");
+
+ log.info("澶勭悊寰俊鍥炶皟锛屽晢鎴疯鍗曞彿: {}, 寰俊浜ゆ槗鍙�: {}, 缁撴灉: {}", outTradeNo, transactionId, resultCode);
+
+ // 妫�鏌ュ箓绛夋��
+ if (isNotifyProcessed("WECHAT", transactionId)) {
+ log.info("寰俊鍥炶皟宸插鐞嗚繃锛屼氦鏄撳彿: {}", transactionId);
+ return;
+ }
+
+ // 璁板綍鍥炶皟鏃ュ織
+ NotifyLog notifyLog = saveNotifyLog("WECHAT", transactionId, rawXml, true);
+
+ try {
+ // 閫氫俊鎴愬姛涓斾笟鍔℃垚鍔�
+ if ("SUCCESS".equals(returnCode) && "SUCCESS".equals(resultCode)) {
+ // 鏌ヨ璁㈠崟鍜屼氦鏄擄紙浣跨敤涓氬姟璁㈠崟鍙锋煡璇級
+ PaymentOrder order = paymentOrderMapper.selectByBizOrderIdAndChannel(outTradeNo, "WECHAT");
+ if (order == null) {
+ log.error("璁㈠崟涓嶅瓨鍦�: {}", outTradeNo);
+ updateNotifyLog(notifyLog.getId(), false, "璁㈠崟涓嶅瓨鍦�");
+ return;
+ }
+
+ PaymentTransaction transaction = paymentTransactionMapper.selectPendingByOrderId(order.getId());
+ if (transaction == null) {
+ log.error("寰呮敮浠樹氦鏄撲笉瀛樺湪锛岃鍗旾D: {}", order.getId());
+ updateNotifyLog(notifyLog.getId(), false, "浜ゆ槗涓嶅瓨鍦�");
+ return;
+ }
+
+ // 鏇存柊浜ゆ槗鐘舵��
+ transaction.setStatus("SUCCEEDED");
+ transaction.setChannelTradeNo(transactionId);
+ transaction.setPaidAt(LocalDateTime.now());
+ transaction.setResponseSnapshot(JSON.toJSONString(params));
+ paymentTransactionMapper.update(transaction);
+
+ // 鏇存柊璁㈠崟鐘舵��
+ order.setStatus("SUCCEEDED");
+ order.setChannelTradeNo(transactionId);
+ order.setPaidAt(LocalDateTime.now());
+ order.setLatestTransactionId(transaction.getId());
+ paymentOrderMapper.update(order);
+
+ // 鏇存柊閫氱煡鏃ュ織
+ notifyLog.setOrderId(order.getId());
+ notifyLog.setTransactionId(transaction.getId());
+ updateNotifyLog(notifyLog.getId(), true, "澶勭悊鎴愬姛");
+
+ // 瑙﹀彂涓氬姟鍥炶皟
+ bizCallbackService.triggerCallback(order, transaction);
+
+ log.info("寰俊鏀粯鍥炶皟澶勭悊鎴愬姛锛岃鍗旾D: {}, 浜ゆ槗ID: {}", order.getId(), transaction.getId());
+ } else {
+ log.warn("寰俊鏀粯澶辫触锛宺eturnCode: {}, resultCode: {}", returnCode, resultCode);
+ updateNotifyLog(notifyLog.getId(), true, "鏀粯澶辫触");
+ }
+ } catch (Exception e) {
+ log.error("澶勭悊寰俊鍥炶皟寮傚父", e);
+ updateNotifyLog(notifyLog.getId(), false, "澶勭悊寮傚父: " + e.getMessage());
+ throw e;
+ }
+ }
+
+ /**
+ * 澶勭悊鏀粯瀹濆洖璋�
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public void processAlipayNotify(Map<String, String> params) {
+ String outTradeNo = params.get("out_trade_no"); // 鍟嗘埛璁㈠崟鍙�
+ String tradeNo = params.get("trade_no"); // 鏀粯瀹濅氦鏄撳彿
+ String tradeStatus = params.get("trade_status");
+
+ log.info("澶勭悊鏀粯瀹濆洖璋冿紝鍟嗘埛璁㈠崟鍙�: {}, 鏀粯瀹濅氦鏄撳彿: {}, 鐘舵��: {}", outTradeNo, tradeNo, tradeStatus);
+
+ // 妫�鏌ュ箓绛夋��
+ if (isNotifyProcessed("ALIPAY", tradeNo)) {
+ log.info("鏀粯瀹濆洖璋冨凡澶勭悊杩囷紝浜ゆ槗鍙�: {}", tradeNo);
+ return;
+ }
+
+ // 璁板綍鍥炶皟鏃ュ織
+ NotifyLog notifyLog = saveNotifyLog("ALIPAY", tradeNo, JSON.toJSONString(params), true);
+
+ try {
+ // 浜ゆ槗鎴愬姛
+ if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
+ // 鏌ヨ璁㈠崟鍜屼氦鏄擄紙浣跨敤涓氬姟璁㈠崟鍙锋煡璇級
+ PaymentOrder order = paymentOrderMapper.selectByBizOrderIdAndChannel(outTradeNo, "ALIPAY");
+ if (order == null) {
+ log.error("璁㈠崟涓嶅瓨鍦�: {}", outTradeNo);
+ updateNotifyLog(notifyLog.getId(), false, "璁㈠崟涓嶅瓨鍦�");
+ return;
+ }
+
+ PaymentTransaction transaction = paymentTransactionMapper.selectPendingByOrderId(order.getId());
+ if (transaction == null) {
+ log.error("寰呮敮浠樹氦鏄撲笉瀛樺湪锛岃鍗旾D: {}", order.getId());
+ updateNotifyLog(notifyLog.getId(), false, "浜ゆ槗涓嶅瓨鍦�");
+ return;
+ }
+
+ // 鏇存柊浜ゆ槗鐘舵��
+ transaction.setStatus("SUCCEEDED");
+ transaction.setChannelTradeNo(tradeNo);
+ transaction.setPaidAt(LocalDateTime.now());
+ transaction.setResponseSnapshot(JSON.toJSONString(params));
+ paymentTransactionMapper.update(transaction);
+
+ // 鏇存柊璁㈠崟鐘舵��
+ order.setStatus("SUCCEEDED");
+ order.setChannelTradeNo(tradeNo);
+ order.setPaidAt(LocalDateTime.now());
+ order.setLatestTransactionId(transaction.getId());
+ paymentOrderMapper.update(order);
+
+ // 鏇存柊閫氱煡鏃ュ織
+ notifyLog.setOrderId(order.getId());
+ notifyLog.setTransactionId(transaction.getId());
+ updateNotifyLog(notifyLog.getId(), true, "澶勭悊鎴愬姛");
+
+ // 瑙﹀彂涓氬姟鍥炶皟
+ bizCallbackService.triggerCallback(order, transaction);
+
+ log.info("鏀粯瀹濆洖璋冨鐞嗘垚鍔燂紝璁㈠崟ID: {}, 浜ゆ槗ID: {}", order.getId(), transaction.getId());
+ } else {
+ log.warn("鏀粯瀹濅氦鏄撶姸鎬佸紓甯�: {}", tradeStatus);
+ updateNotifyLog(notifyLog.getId(), true, "鐘舵��: " + tradeStatus);
+ }
+ } catch (Exception e) {
+ log.error("澶勭悊鏀粯瀹濆洖璋冨紓甯�", e);
+ updateNotifyLog(notifyLog.getId(), false, "澶勭悊寮傚父: " + e.getMessage());
+ throw e;
+ }
+ }
+
+ /**
+ * 妫�鏌ラ�氱煡鏄惁宸插鐞嗭紙骞傜瓑鎬э級
+ */
+ private boolean isNotifyProcessed(String channel, String notifyId) {
+ return notifyLogMapper.selectProcessedCount(channel, notifyId) > 0;
+ }
+
+ /**
+ * 淇濆瓨閫氱煡鏃ュ織
+ */
+ private NotifyLog saveNotifyLog(String channel, String notifyId, String payload, boolean verified) {
+ NotifyLog log = new NotifyLog();
+ log.setId(SnowflakeIdGenerator.generateId());
+ log.setChannel(channel);
+ log.setNotifyIdOrSerial(notifyId);
+ log.setPayload(payload);
+ log.setVerified(verified);
+ log.setProcessed(false);
+ log.setCreatedAt(LocalDateTime.now());
+ notifyLogMapper.insert(log);
+ return log;
+ }
+
+ /**
+ * 鏇存柊閫氱煡鏃ュ織
+ */
+ private void updateNotifyLog(Long id, boolean processed, String result) {
+ NotifyLog log = new NotifyLog();
+ log.setId(id);
+ log.setProcessed(processed);
+ log.setResult(result);
+ notifyLogMapper.update(log);
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/application/service/PaymentService.java b/dryad-payment/src/main/java/com/ruoyi/payment/application/service/PaymentService.java
new file mode 100644
index 0000000..3b44a9b
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/application/service/PaymentService.java
@@ -0,0 +1,338 @@
+package com.ruoyi.payment.application.service;
+
+import com.alibaba.fastjson.JSON;
+import com.ruoyi.payment.domain.enums.ClientType;
+import com.ruoyi.payment.domain.enums.OrderStatus;
+import com.ruoyi.payment.domain.enums.PayChannel;
+import com.ruoyi.payment.domain.enums.TransactionStatus;
+import com.ruoyi.payment.domain.model.PaymentOrder;
+import com.ruoyi.payment.domain.model.PaymentTransaction;
+import com.ruoyi.payment.infrastructure.channel.alipay.AlipayF2FClient;
+import com.ruoyi.payment.infrastructure.channel.alipay.AlipayThirdPartyClient;
+import com.ruoyi.payment.infrastructure.channel.wechat.WxPayV2Client;
+import com.ruoyi.payment.infrastructure.config.AlipayConfig;
+import com.ruoyi.payment.infrastructure.config.QrCodeConfig;
+import com.ruoyi.payment.infrastructure.persistence.mapper.PaymentOrderMapper;
+import com.ruoyi.payment.infrastructure.persistence.mapper.PaymentTransactionMapper;
+import com.ruoyi.payment.infrastructure.util.QrCodeUtil;
+import com.ruoyi.payment.infrastructure.util.SnowflakeIdGenerator;
+import com.ruoyi.payment.interfaces.dto.PaymentRequest;
+import com.ruoyi.payment.interfaces.dto.PaymentResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 鏀粯搴旂敤鏈嶅姟
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@Service
+public class PaymentService {
+
+ @Autowired
+ private PaymentOrderMapper paymentOrderMapper;
+
+ @Autowired
+ private PaymentTransactionMapper paymentTransactionMapper;
+
+ @Autowired
+ private QrCodeUtil qrCodeUtil;
+
+ @Autowired
+ private QrCodeConfig qrCodeConfig;
+
+ @Autowired
+ private WxPayV2Client wxPayV2Client;
+
+ @Autowired
+ private AlipayF2FClient alipayF2FClient;
+
+ @Autowired
+ private AlipayThirdPartyClient alipayThirdPartyClient;
+
+ @Autowired
+ private AlipayConfig alipayConfig;
+
+ /**
+ * 鍙戣捣寰俊Native鏀粯
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public PaymentResponse createWechatNativePayment(PaymentRequest request) {
+ log.info("鍙戣捣寰俊Native鏀粯锛岃姹傚弬鏁�: {}", JSON.toJSONString(request));
+
+ // 1. 鏌ヨ鎴栧垱寤鸿鍗�
+ PaymentOrder order = getOrCreateOrder(request, PayChannel.WECHAT.getCode());
+
+ // 2. 妫�鏌ユ槸鍚﹀瓨鍦ㄥ緟鏀粯浜ゆ槗锛堝悓涓�璁㈠崟鍙厑璁镐竴绗旇繘琛屼腑锛�
+ PaymentTransaction existingTransaction = paymentTransactionMapper.selectPendingByOrderId(order.getId());
+ if (existingTransaction != null) {
+ log.info("璁㈠崟{}宸插瓨鍦ㄥ緟鏀粯浜ゆ槗{}锛岀洿鎺ヨ繑鍥�", order.getId(), existingTransaction.getId());
+ return buildPaymentResponse(order, existingTransaction);
+ }
+
+ // 3. 璋冪敤寰俊涓嬪崟鎺ュ彛锛堣繖閲屽厛妯℃嫙锛�
+ String codeUrl = callWechatUnifiedOrder(order);
+
+ // 4. 鐢熸垚浜岀淮鐮丅ase64
+ String qrBase64 = qrCodeUtil.generateQrCodeBase64(codeUrl, qrCodeConfig.getSize());
+
+ // 5. 鍒涘缓浜ゆ槗璁板綍
+ PaymentTransaction transaction = new PaymentTransaction();
+ transaction.setId(SnowflakeIdGenerator.generateId());
+ transaction.setOrderId(order.getId());
+ transaction.setChannel(PayChannel.WECHAT.getCode());
+ transaction.setClientType(ClientType.NATIVE.getCode());
+ transaction.setStatus(TransactionStatus.PENDING.getCode());
+ transaction.setCodeOrQr(codeUrl);
+ transaction.setQrBase64(qrBase64);
+ transaction.setCreatedAt(LocalDateTime.now());
+
+ // 鏋勯�犺姹傚弬鏁板揩鐓�
+ Map<String, Object> requestParams = new HashMap<>();
+ requestParams.put("bizOrderId", request.getBizOrderId());
+ requestParams.put("amount", request.getAmount());
+ requestParams.put("subject", request.getSubject());
+ transaction.setRequestParams(JSON.toJSONString(requestParams));
+
+ paymentTransactionMapper.insert(transaction);
+
+ // 6. 鏇存柊璁㈠崟鏈�鏂颁氦鏄揑D
+ order.setLatestTransactionId(transaction.getId());
+ paymentOrderMapper.update(order);
+
+ log.info("寰俊Native鏀粯鍒涘缓鎴愬姛锛岃鍗旾D: {}, 浜ゆ槗ID: {}", order.getId(), transaction.getId());
+
+ return buildPaymentResponse(order, transaction);
+ }
+
+ /**
+ * 鍙戣捣鏀粯瀹濆綋闈粯
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public PaymentResponse createAlipayPrecreate(PaymentRequest request) {
+ log.info("鍙戣捣鏀粯瀹濆綋闈粯锛岃姹傚弬鏁�: {}", JSON.toJSONString(request));
+
+ // 1. 鏌ヨ鎴栧垱寤鸿鍗�
+ PaymentOrder order = getOrCreateOrder(request, PayChannel.ALIPAY.getCode());
+
+ // 2. 妫�鏌ユ槸鍚﹀瓨鍦ㄥ緟鏀粯浜ゆ槗
+ PaymentTransaction existingTransaction = paymentTransactionMapper.selectPendingByOrderId(order.getId());
+ if (existingTransaction != null) {
+ log.info("璁㈠崟{}宸插瓨鍦ㄥ緟鏀粯浜ゆ槗{}锛岀洿鎺ヨ繑鍥�", order.getId(), existingTransaction.getId());
+ return buildPaymentResponse(order, existingTransaction);
+ }
+
+ // 3. 璋冪敤鏀粯瀹濅笅鍗曟帴鍙o紙杩欓噷鍏堟ā鎷燂級
+ String qrCode = callAlipayPrecreate(order);
+
+ // 4. 鐢熸垚浜岀淮鐮丅ase64
+ String qrBase64 = qrCodeUtil.generateQrCodeBase64(qrCode, qrCodeConfig.getSize());
+
+ // 5. 鍒涘缓浜ゆ槗璁板綍
+ PaymentTransaction transaction = new PaymentTransaction();
+ transaction.setId(SnowflakeIdGenerator.generateId());
+ transaction.setOrderId(order.getId());
+ transaction.setChannel(PayChannel.ALIPAY.getCode());
+ transaction.setClientType(ClientType.ALIPAY_PRECREATE.getCode());
+ transaction.setStatus(TransactionStatus.PENDING.getCode());
+ transaction.setCodeOrQr(qrCode);
+ transaction.setQrBase64(qrBase64);
+ transaction.setCreatedAt(LocalDateTime.now());
+
+ Map<String, Object> requestParams = new HashMap<>();
+ requestParams.put("bizOrderId", request.getBizOrderId());
+ requestParams.put("amount", request.getAmount());
+ requestParams.put("subject", request.getSubject());
+ transaction.setRequestParams(JSON.toJSONString(requestParams));
+
+ paymentTransactionMapper.insert(transaction);
+
+ // 6. 鏇存柊璁㈠崟
+ order.setLatestTransactionId(transaction.getId());
+ paymentOrderMapper.update(order);
+
+ log.info("鏀粯瀹濆綋闈粯鍒涘缓鎴愬姛锛岃鍗旾D: {}, 浜ゆ槗ID: {}", order.getId(), transaction.getId());
+
+ return buildPaymentResponse(order, transaction);
+ }
+
+ /**
+ * 鍙戣捣鏀粯瀹濆綋闈粯锛堢涓夋柟鎺ュ彛锛�
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public PaymentResponse createAlipayThirdPartyPrecreate(PaymentRequest request) {
+ log.info("鍙戣捣鏀粯瀹濆綋闈粯锛堢涓夋柟鎺ュ彛锛夛紝璇锋眰鍙傛暟: {}", JSON.toJSONString(request));
+
+ // 1. 鏌ヨ鎴栧垱寤鸿鍗�
+ PaymentOrder order = getOrCreateOrder(request, PayChannel.ALIPAY.getCode());
+
+ // 2. 妫�鏌ユ槸鍚﹀瓨鍦ㄥ緟鏀粯浜ゆ槗
+ PaymentTransaction existingTransaction = paymentTransactionMapper.selectPendingByOrderId(order.getId());
+ if (existingTransaction != null) {
+ log.info("璁㈠崟{}宸插瓨鍦ㄥ緟鏀粯浜ゆ槗{}锛岀洿鎺ヨ繑鍥�", order.getId(), existingTransaction.getId());
+ return buildPaymentResponse(order, existingTransaction);
+ }
+
+ // 3. 璋冪敤绗笁鏂规敮浠樺疂鎺ュ彛鐢熸垚鏀粯URL
+ String payUrl = callAlipayThirdPartyPrecreate(order, request);
+
+ // 4. 鐢熸垚浜岀淮鐮丅ase64
+ String qrBase64 = qrCodeUtil.generateQrCodeBase64(payUrl, qrCodeConfig.getSize());
+
+ // 5. 鍒涘缓浜ゆ槗璁板綍
+ PaymentTransaction transaction = new PaymentTransaction();
+ transaction.setId(SnowflakeIdGenerator.generateId());
+ transaction.setOrderId(order.getId());
+ transaction.setChannel(PayChannel.ALIPAY.getCode());
+ transaction.setClientType(ClientType.ALIPAY_PRECREATE.getCode());
+ transaction.setStatus(TransactionStatus.PENDING.getCode());
+ transaction.setCodeOrQr(payUrl);
+ transaction.setQrBase64(qrBase64);
+ transaction.setCreatedAt(LocalDateTime.now());
+
+ Map<String, Object> requestParams = new HashMap<>();
+ requestParams.put("bizOrderId", request.getBizOrderId());
+ requestParams.put("amount", request.getAmount());
+ requestParams.put("subject", request.getSubject());
+ requestParams.put("thirdParty", true);
+ transaction.setRequestParams(JSON.toJSONString(requestParams));
+
+ paymentTransactionMapper.insert(transaction);
+
+ // 6. 鏇存柊璁㈠崟
+ order.setLatestTransactionId(transaction.getId());
+ paymentOrderMapper.update(order);
+
+ log.info("鏀粯瀹濆綋闈粯锛堢涓夋柟鎺ュ彛锛夊垱寤烘垚鍔燂紝璁㈠崟ID: {}, 浜ゆ槗ID: {}", order.getId(), transaction.getId());
+
+ return buildPaymentResponse(order, transaction);
+ }
+
+ /**
+ * 鏌ヨ鎴栧垱寤鸿鍗�
+ */
+ private PaymentOrder getOrCreateOrder(PaymentRequest request, String channel) {
+ // 鍏堟煡璇㈡槸鍚﹀瓨鍦�
+ PaymentOrder existingOrder = paymentOrderMapper.selectByBizOrderIdAndChannel(
+ request.getBizOrderId(), channel);
+
+ if (existingOrder != null) {
+ // 濡傛灉璁㈠崟宸茶繃鏈燂紝鏇存柊鐘舵��
+ if (existingOrder.isExpired() && OrderStatus.PENDING.getCode().equals(existingOrder.getStatus())) {
+ existingOrder.setStatus(OrderStatus.EXPIRED.getCode());
+ paymentOrderMapper.update(existingOrder);
+ }
+ return existingOrder;
+ }
+
+ // 鍒涘缓鏂拌鍗�
+ PaymentOrder order = new PaymentOrder();
+ order.setId(SnowflakeIdGenerator.generateId());
+ order.setBizOrderId(request.getBizOrderId());
+ order.setAmount(request.getAmount());
+ order.setCurrency("CNY");
+ order.setChannel(channel);
+ order.setStatus(OrderStatus.PENDING.getCode());
+ order.setSubject(request.getSubject());
+ order.setDescription(request.getDescription());
+ order.setCallbackUrl(request.getCallbackUrl());
+ order.setExpireAt(LocalDateTime.now().plusHours(2)); // 2灏忔椂杩囨湡
+ order.setVersion(0);
+ order.setCreatedAt(LocalDateTime.now());
+ order.setUpdatedAt(LocalDateTime.now());
+
+ paymentOrderMapper.insert(order);
+
+ return order;
+ }
+
+ /**
+ * 璋冪敤寰俊缁熶竴涓嬪崟鎺ュ彛
+ */
+ private String callWechatUnifiedOrder(PaymentOrder order) {
+ try {
+ return wxPayV2Client.unifiedOrder(order);
+ } catch (Exception e) {
+ log.error("璋冪敤寰俊缁熶竴涓嬪崟鎺ュ彛澶辫触", e);
+ throw new RuntimeException("寰俊涓嬪崟澶辫触: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 璋冪敤鏀粯瀹濆綋闈粯鎺ュ彛
+ */
+ private String callAlipayPrecreate(PaymentOrder order) {
+ try {
+ return alipayF2FClient.precreate(order);
+ } catch (Exception e) {
+ log.error("璋冪敤鏀粯瀹濆綋闈粯鎺ュ彛澶辫触", e);
+ throw new RuntimeException("鏀粯瀹濅笅鍗曞け璐�: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 璋冪敤绗笁鏂规敮浠樺疂鎺ュ彛鐢熸垚鏀粯URL
+ */
+ private String callAlipayThirdPartyPrecreate(PaymentOrder order, PaymentRequest request) {
+ try {
+ // 浣跨敤AlipayConfig涓厤缃殑鍥炶皟鍦板潃
+ String notifyUrl = alipayConfig.getNotifyUrl();
+ String outTradeNo = String.valueOf(order.getId());
+ Integer totalFee = order.getAmount(); // 鍗曚綅锛氬垎
+ String serviceOrdId = request.getBizOrderId(); // 涓氬姟璁㈠崟ID
+
+ return alipayThirdPartyClient.createQrCodeUrl(notifyUrl, outTradeNo, totalFee, serviceOrdId);
+ } catch (Exception e) {
+ log.error("璋冪敤绗笁鏂规敮浠樺疂鎺ュ彛澶辫触", e);
+ throw new RuntimeException("绗笁鏂规敮浠樺疂涓嬪崟澶辫触: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 鏋勯�犳敮浠樺搷搴�
+ */
+ private PaymentResponse buildPaymentResponse(PaymentOrder order, PaymentTransaction transaction) {
+ PaymentResponse response = new PaymentResponse();
+ response.setOrderId(order.getId());
+ response.setTransactionId(transaction.getId());
+ response.setStatus(order.getStatus());
+ response.setQrBase64(transaction.getQrBase64());
+ response.setExpireAt(order.getExpireAt());
+ return response;
+ }
+
+ /**
+ * 鏌ヨ璁㈠崟
+ */
+ public PaymentOrder getOrder(Long orderId) {
+ return paymentOrderMapper.selectById(orderId);
+ }
+
+ /**
+ * 鏌ヨ鏈�鏂颁氦鏄�
+ */
+ public PaymentTransaction getLatestTransaction(Long orderId) {
+ return paymentTransactionMapper.selectLatestByOrderId(orderId);
+ }
+
+ /**
+ * 鏌ヨ鏀粯瀹濈涓夋柟浜ゆ槗鐘舵��
+ */
+ public String queryAlipayThirdPartyTradeStatus(Long orderId) {
+ try {
+ // 浣跨敤璁㈠崟ID浣滀负鍟嗘埛璁㈠崟鍙�
+ String outTradeNo = String.valueOf(orderId);
+ return alipayThirdPartyClient.queryTradeStatus(outTradeNo);
+ } catch (Exception e) {
+ log.error("鏌ヨ鏀粯瀹濈涓夋柟浜ゆ槗鐘舵�佸け璐ワ紝璁㈠崟ID: {}", orderId, e);
+ throw new RuntimeException("鏌ヨ浜ゆ槗鐘舵�佸け璐�: " + e.getMessage(), e);
+ }
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/common/AjaxResult.java b/dryad-payment/src/main/java/com/ruoyi/payment/common/AjaxResult.java
new file mode 100644
index 0000000..02d7796
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/common/AjaxResult.java
@@ -0,0 +1,87 @@
+package com.ruoyi.payment.common;
+
+import java.io.Serializable;
+
+/**
+ * 缁熶竴鍝嶅簲缁撴灉
+ *
+ * @author ruoyi
+ */
+public class AjaxResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /** 鐘舵�佺爜 */
+ private int code;
+
+ /** 杩斿洖娑堟伅 */
+ private String msg;
+
+ /** 杩斿洖鏁版嵁 */
+ private Object data;
+
+ public AjaxResult() {
+ }
+
+ public AjaxResult(int code, String msg) {
+ this.code = code;
+ this.msg = msg;
+ }
+
+ public AjaxResult(int code, String msg, Object data) {
+ this.code = code;
+ this.msg = msg;
+ this.data = data;
+ }
+
+ public static AjaxResult success() {
+ return new AjaxResult(200, "鎿嶄綔鎴愬姛");
+ }
+
+ public static AjaxResult success(String msg) {
+ return new AjaxResult(200, msg);
+ }
+
+ public static AjaxResult success(Object data) {
+ return new AjaxResult(200, "鎿嶄綔鎴愬姛", data);
+ }
+
+ public static AjaxResult success(String msg, Object data) {
+ return new AjaxResult(200, msg, data);
+ }
+
+ public static AjaxResult error() {
+ return new AjaxResult(500, "鎿嶄綔澶辫触");
+ }
+
+ public static AjaxResult error(String msg) {
+ return new AjaxResult(500, msg);
+ }
+
+ public static AjaxResult error(int code, String msg) {
+ return new AjaxResult(code, msg);
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public void setCode(int code) {
+ this.code = code;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+ public void setMsg(String msg) {
+ this.msg = msg;
+ }
+
+ public Object getData() {
+ return data;
+ }
+
+ public void setData(Object data) {
+ this.data = data;
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/config/PaymentAutoConfiguration.java b/dryad-payment/src/main/java/com/ruoyi/payment/config/PaymentAutoConfiguration.java
new file mode 100644
index 0000000..60ffce3
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/config/PaymentAutoConfiguration.java
@@ -0,0 +1,48 @@
+package com.ruoyi.payment.config;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+
+/**
+ * 鏀粯妯″潡鑷姩閰嶇疆绫�
+ *
+ * 浣跨敤鏂瑰紡锛�
+ * 1. 浣滀负缁勪欢闆嗘垚鍒板叾浠栭」鐩椂锛屽湪涓婚」鐩殑 pom.xml 涓坊鍔犱緷璧栵細
+ * <dependency>
+ * <groupId>com.ruoyi</groupId>
+ * <artifactId>dryad-payment</artifactId>
+ * <version>1.0.0</version>
+ * </dependency>
+ *
+ * 2. 鍦ㄤ富椤圭洰鐨� application.yml 涓厤缃細
+ * payment:
+ * enabled: true # 鍚敤鏀粯妯″潡
+ * wechat: ... # 寰俊閰嶇疆
+ * alipay: ... # 鏀粯瀹濋厤缃�
+ *
+ * @author ruoyi
+ */
+@Configuration
+@ConditionalOnProperty(prefix = "payment", name = "enabled", havingValue = "true", matchIfMissing = true)
+@ComponentScan(
+ basePackages = {
+ "com.ruoyi.payment.interfaces.controller",
+ "com.ruoyi.payment.application.service",
+ "com.ruoyi.payment.domain.service",
+ "com.ruoyi.payment.infrastructure"
+ },
+ excludeFilters = @ComponentScan.Filter(
+ type = FilterType.REGEX,
+ pattern = "com\\.ruoyi\\.payment\\.example\\..*"
+ )
+)
+public class PaymentAutoConfiguration {
+
+ public PaymentAutoConfiguration() {
+ System.out.println("=================================");
+ System.out.println("鏀粯妯″潡宸插惎鐢�");
+ System.out.println("=================================");
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/ClientType.java b/dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/ClientType.java
new file mode 100644
index 0000000..915d882
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/ClientType.java
@@ -0,0 +1,40 @@
+package com.ruoyi.payment.domain.enums;
+
+/**
+ * 瀹㈡埛绔被鍨嬫灇涓�
+ *
+ * @author ruoyi
+ */
+public enum ClientType {
+
+ /** 寰俊Native */
+ NATIVE("NATIVE", "寰俊Native"),
+
+ /** 鏀粯瀹濆綋闈粯 */
+ ALIPAY_PRECREATE("ALIPAY_PRECREATE", "鏀粯瀹濆綋闈粯");
+
+ private final String code;
+ private final String desc;
+
+ ClientType(String code, String desc) {
+ this.code = code;
+ this.desc = desc;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public static ClientType fromCode(String code) {
+ for (ClientType type : values()) {
+ if (type.code.equals(code)) {
+ return type;
+ }
+ }
+ throw new IllegalArgumentException("鏈煡鐨勫鎴风绫诲瀷: " + code);
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/OrderStatus.java b/dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/OrderStatus.java
new file mode 100644
index 0000000..8d38c78
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/OrderStatus.java
@@ -0,0 +1,52 @@
+package com.ruoyi.payment.domain.enums;
+
+/**
+ * 璁㈠崟鐘舵�佹灇涓�
+ *
+ * @author ruoyi
+ */
+public enum OrderStatus {
+
+ /** 鍒濆鍖� */
+ INIT("INIT", "鍒濆鍖�"),
+
+ /** 寰呮敮浠� */
+ PENDING("PENDING", "寰呮敮浠�"),
+
+ /** 鏀粯鎴愬姛 */
+ SUCCEEDED("SUCCEEDED", "鏀粯鎴愬姛"),
+
+ /** 鏀粯澶辫触 */
+ FAILED("FAILED", "鏀粯澶辫触"),
+
+ /** 宸插叧闂� */
+ CANCELED("CANCELED", "宸插叧闂�"),
+
+ /** 宸茶繃鏈� */
+ EXPIRED("EXPIRED", "宸茶繃鏈�");
+
+ private final String code;
+ private final String desc;
+
+ OrderStatus(String code, String desc) {
+ this.code = code;
+ this.desc = desc;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public static OrderStatus fromCode(String code) {
+ for (OrderStatus status : values()) {
+ if (status.code.equals(code)) {
+ return status;
+ }
+ }
+ throw new IllegalArgumentException("鏈煡鐨勮鍗曠姸鎬�: " + code);
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/PayChannel.java b/dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/PayChannel.java
new file mode 100644
index 0000000..189c3b8
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/PayChannel.java
@@ -0,0 +1,40 @@
+package com.ruoyi.payment.domain.enums;
+
+/**
+ * 鏀粯娓犻亾鏋氫妇
+ *
+ * @author ruoyi
+ */
+public enum PayChannel {
+
+ /** 寰俊鏀粯 */
+ WECHAT("WECHAT", "寰俊鏀粯"),
+
+ /** 鏀粯瀹� */
+ ALIPAY("ALIPAY", "鏀粯瀹�");
+
+ private final String code;
+ private final String desc;
+
+ PayChannel(String code, String desc) {
+ this.code = code;
+ this.desc = desc;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public static PayChannel fromCode(String code) {
+ for (PayChannel channel : values()) {
+ if (channel.code.equals(code)) {
+ return channel;
+ }
+ }
+ throw new IllegalArgumentException("鏈煡鐨勬敮浠樻笭閬�: " + code);
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/TransactionStatus.java b/dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/TransactionStatus.java
new file mode 100644
index 0000000..4cd29bb
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/domain/enums/TransactionStatus.java
@@ -0,0 +1,46 @@
+package com.ruoyi.payment.domain.enums;
+
+/**
+ * 浜ゆ槗鐘舵�佹灇涓�
+ *
+ * @author ruoyi
+ */
+public enum TransactionStatus {
+
+ /** 寰呮敮浠� */
+ PENDING("PENDING", "寰呮敮浠�"),
+
+ /** 鏀粯鎴愬姛 */
+ SUCCEEDED("SUCCEEDED", "鏀粯鎴愬姛"),
+
+ /** 鏀粯澶辫触 */
+ FAILED("FAILED", "鏀粯澶辫触"),
+
+ /** 宸插彇娑� */
+ CANCELED("CANCELED", "宸插彇娑�");
+
+ private final String code;
+ private final String desc;
+
+ TransactionStatus(String code, String desc) {
+ this.code = code;
+ this.desc = desc;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public static TransactionStatus fromCode(String code) {
+ for (TransactionStatus status : values()) {
+ if (status.code.equals(code)) {
+ return status;
+ }
+ }
+ throw new IllegalArgumentException("鏈煡鐨勪氦鏄撶姸鎬�: " + code);
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/domain/event/PaymentSuccessEvent.java b/dryad-payment/src/main/java/com/ruoyi/payment/domain/event/PaymentSuccessEvent.java
new file mode 100644
index 0000000..907123a
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/domain/event/PaymentSuccessEvent.java
@@ -0,0 +1,50 @@
+package com.ruoyi.payment.domain.event;
+
+import com.ruoyi.payment.domain.model.PaymentOrder;
+import com.ruoyi.payment.domain.model.PaymentTransaction;
+import lombok.Getter;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * 鏀粯鎴愬姛浜嬩欢
+ *
+ * 浣跨敤鏂瑰紡锛�
+ * 1. 浣滀负jar鍖呴泦鎴愭椂锛屽湪涓婚」鐩腑鐩戝惉姝や簨浠讹細
+ * @EventListener
+ * public void handlePaymentSuccess(PaymentSuccessEvent event) {
+ * PaymentOrder order = event.getOrder();
+ * PaymentTransaction transaction = event.getTransaction();
+ * // 澶勭悊涓氬姟閫昏緫
+ * }
+ *
+ * 2. 鎴栬�呭疄鐜� PaymentCallback 鎺ュ彛杩涜鍥炶皟
+ *
+ * @author ruoyi
+ */
+@Getter
+public class PaymentSuccessEvent extends ApplicationEvent {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 鏀粯璁㈠崟
+ */
+ private final PaymentOrder order;
+
+ /**
+ * 鏀粯浜ゆ槗
+ */
+ private final PaymentTransaction transaction;
+
+ /**
+ * 鏀粯娓犻亾锛圵ECHAT/ALIPAY锛�
+ */
+ private final String channel;
+
+ public PaymentSuccessEvent(Object source, PaymentOrder order, PaymentTransaction transaction) {
+ super(source);
+ this.order = order;
+ this.transaction = transaction;
+ this.channel = order.getChannel();
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/BizCallbackLog.java b/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/BizCallbackLog.java
new file mode 100644
index 0000000..39a11db
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/BizCallbackLog.java
@@ -0,0 +1,49 @@
+package com.ruoyi.payment.domain.model;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 涓氬姟鍥炶皟鏃ュ織
+ *
+ * @author ruoyi
+ */
+@Data
+public class BizCallbackLog implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /** 鏃ュ織ID */
+ private Long id;
+
+ /** 璁㈠崟ID */
+ private Long orderId;
+
+ /** 浜ゆ槗ID */
+ private Long transactionId;
+
+ /** 鍥炶皟鍦板潃 */
+ private String callbackUrl;
+
+ /** 鍥炶皟璇锋眰浣� */
+ private String payload;
+
+ /** HTTP鐘舵�佺爜 */
+ private Integer httpStatus;
+
+ /** 鍝嶅簲鍐呭 */
+ private String response;
+
+ /** 鏄惁鎴愬姛 */
+ private Boolean success;
+
+ /** 閲嶈瘯娆℃暟 */
+ private Integer retryCount;
+
+ /** 鏈�鍚庨噸璇曟椂闂� */
+ private LocalDateTime lastRetryAt;
+
+ /** 鍒涘缓鏃堕棿 */
+ private LocalDateTime createdAt;
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/GoodsDetail.java b/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/GoodsDetail.java
new file mode 100644
index 0000000..1eeede4
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/GoodsDetail.java
@@ -0,0 +1,51 @@
+package com.ruoyi.payment.domain.model;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 鍟嗗搧鏄庣粏
+ *
+ * @author ruoyi
+ */
+@Data
+public class GoodsDetail implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /** 鍟嗗搧ID */
+ private String goodsId;
+
+ /** 鍟嗗搧鍚嶇О */
+ private String goodsName;
+
+ /** 鍟嗗搧鏁伴噺 */
+ private Integer quantity;
+
+ /** 鍟嗗搧鍗曚环锛堝垎锛� */
+ private Integer price;
+
+ /** 鍟嗗搧绫诲埆 */
+ private String goodsCategory;
+
+ /** 鍟嗗搧鎻忚堪 */
+ private String body;
+
+ public GoodsDetail() {}
+
+ public GoodsDetail(String goodsId, String goodsName, Integer quantity, Integer price) {
+ this.goodsId = goodsId;
+ this.goodsName = goodsName;
+ this.quantity = quantity;
+ this.price = price;
+ }
+
+ public GoodsDetail(String goodsId, String goodsName, Integer quantity, Integer price, String goodsCategory, String body) {
+ this.goodsId = goodsId;
+ this.goodsName = goodsName;
+ this.quantity = quantity;
+ this.price = price;
+ this.goodsCategory = goodsCategory;
+ this.body = body;
+ }
+}
\ No newline at end of file
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/NotifyLog.java b/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/NotifyLog.java
new file mode 100644
index 0000000..6975aa6
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/NotifyLog.java
@@ -0,0 +1,46 @@
+package com.ruoyi.payment.domain.model;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 娓犻亾鍥炶皟鏃ュ織
+ *
+ * @author ruoyi
+ */
+@Data
+public class NotifyLog implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /** 鏃ュ織ID */
+ private Long id;
+
+ /** 鏀粯娓犻亾 */
+ private String channel;
+
+ /** 娓犻亾閫氱煡鍞竴鏍囪瘑 */
+ private String notifyIdOrSerial;
+
+ /** 璁㈠崟ID */
+ private Long orderId;
+
+ /** 浜ゆ槗ID */
+ private Long transactionId;
+
+ /** 鍥炶皟鍘熷鎶ユ枃 */
+ private String payload;
+
+ /** 绛惧悕楠岃瘉鏄惁閫氳繃 */
+ private Boolean verified;
+
+ /** 鏄惁宸插鐞� */
+ private Boolean processed;
+
+ /** 澶勭悊缁撴灉 */
+ private String result;
+
+ /** 鎺ユ敹鏃堕棿 */
+ private LocalDateTime createdAt;
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/OperationAudit.java b/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/OperationAudit.java
new file mode 100644
index 0000000..35ebdf3
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/OperationAudit.java
@@ -0,0 +1,40 @@
+package com.ruoyi.payment.domain.model;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 鎿嶄綔瀹¤
+ *
+ * @author ruoyi
+ */
+@Data
+public class OperationAudit implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /** 瀹¤ID */
+ private Long id;
+
+ /** 鎿嶄綔浜� */
+ private String operator;
+
+ /** 鎿嶄綔绫诲瀷 */
+ private String operationType;
+
+ /** 璁㈠崟ID */
+ private Long orderId;
+
+ /** 浜ゆ槗ID */
+ private Long transactionId;
+
+ /** 鎿嶄綔鍙傛暟 */
+ private String params;
+
+ /** 鏄惁閫氳繃 */
+ private Boolean approved;
+
+ /** 鎿嶄綔鏃堕棿 */
+ private LocalDateTime createdAt;
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/PaymentOrder.java b/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/PaymentOrder.java
new file mode 100644
index 0000000..0050670
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/PaymentOrder.java
@@ -0,0 +1,88 @@
+package com.ruoyi.payment.domain.model;
+
+import lombok.Data;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 鏀粯璁㈠崟 - 鑱氬悎鏍�
+ *
+ * @author ruoyi
+ */
+@Data
+public class PaymentOrder implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /** 璁㈠崟ID */
+ private Long id;
+
+ /** 涓氬姟璁㈠崟鍙� */
+ private String bizOrderId;
+
+ /** 閲戦锛堝垎锛� */
+ private Integer amount;
+
+ /** 甯佺 */
+ private String currency;
+
+ /** 鏀粯娓犻亾 */
+ private String channel;
+
+ /** 璁㈠崟鐘舵�� */
+ private String status;
+
+ /** 璁㈠崟鏍囬 */
+ private String subject;
+
+ /** 璁㈠崟鎻忚堪 */
+ private String description;
+
+ /** 涓氬姟鍥炶皟鍦板潃 */
+ private String callbackUrl;
+
+ /** 杩囨湡鏃堕棿 */
+ private LocalDateTime expireAt;
+
+ /** 鏈�鏂颁氦鏄揑D */
+ private Long latestTransactionId;
+
+ /** 娓犻亾浜ゆ槗鍙� */
+ private String channelTradeNo;
+
+ /** 鏀粯鎴愬姛鏃堕棿 */
+ private LocalDateTime paidAt;
+
+ /** 涔愯閿佺増鏈彿 */
+ private Integer version;
+
+ /** 鍒涘缓鏃堕棿 */
+ private LocalDateTime createdAt;
+
+ /** 鏇存柊鏃堕棿 */
+ private LocalDateTime updatedAt;
+
+ /** 鍟嗗搧鏄庣粏鍒楄〃 */
+ private List<GoodsDetail> goodsDetails;
+
+ /**
+ * 鍒ゆ柇璁㈠崟鏄惁宸茶繃鏈�
+ */
+ public boolean isExpired() {
+ return expireAt != null && LocalDateTime.now().isAfter(expireAt);
+ }
+
+ /**
+ * 鍒ゆ柇璁㈠崟鏄惁鍙互鏀粯
+ */
+ public boolean canPay() {
+ return "PENDING".equals(status) && !isExpired();
+ }
+
+ /**
+ * 鍒ゆ柇璁㈠崟鏄惁宸叉垚鍔�
+ */
+ public boolean isSuccess() {
+ return "SUCCEEDED".equals(status);
+ }
+}
\ No newline at end of file
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/PaymentTransaction.java b/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/PaymentTransaction.java
new file mode 100644
index 0000000..143a93a
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/domain/model/PaymentTransaction.java
@@ -0,0 +1,66 @@
+package com.ruoyi.payment.domain.model;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 鏀粯浜ゆ槗娴佹按
+ *
+ * @author ruoyi
+ */
+@Data
+public class PaymentTransaction implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /** 浜ゆ槗ID */
+ private Long id;
+
+ /** 璁㈠崟ID */
+ private Long orderId;
+
+ /** 鏀粯娓犻亾 */
+ private String channel;
+
+ /** 瀹㈡埛绔被鍨� */
+ private String clientType;
+
+ /** 浜ゆ槗鐘舵�� */
+ private String status;
+
+ /** 浜岀淮鐮佸唴瀹� */
+ private String codeOrQr;
+
+ /** Base64浜岀淮鐮佸浘鐗� */
+ private String qrBase64;
+
+ /** 璇锋眰鍙傛暟蹇収 */
+ private String requestParams;
+
+ /** 鍝嶅簲蹇収 */
+ private String responseSnapshot;
+
+ /** 娓犻亾浜ゆ槗鍙� */
+ private String channelTradeNo;
+
+ /** 鍒涘缓鏃堕棿 */
+ private LocalDateTime createdAt;
+
+ /** 鏀粯瀹屾垚鏃堕棿 */
+ private LocalDateTime paidAt;
+
+ /**
+ * 鍒ゆ柇浜ゆ槗鏄惁寰呮敮浠�
+ */
+ public boolean isPending() {
+ return "PENDING".equals(status);
+ }
+
+ /**
+ * 鍒ゆ柇浜ゆ槗鏄惁宸叉垚鍔�
+ */
+ public boolean isSuccess() {
+ return "SUCCEEDED".equals(status);
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/example/PaymentIntegrationExample.java b/dryad-payment/src/main/java/com/ruoyi/payment/example/PaymentIntegrationExample.java
new file mode 100644
index 0000000..4c4a5eb
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/example/PaymentIntegrationExample.java
@@ -0,0 +1,140 @@
+package com.ruoyi.payment.example;
+
+import com.ruoyi.payment.domain.event.PaymentSuccessEvent;
+import com.ruoyi.payment.domain.model.PaymentOrder;
+import com.ruoyi.payment.domain.model.PaymentTransaction;
+import com.ruoyi.payment.interfaces.callback.PaymentCallback;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鏀粯妯″潡闆嗘垚浣跨敤绀轰緥
+ *
+ * 褰撴敮浠樻ā鍧椾綔涓簀ar鍖呴泦鎴愬埌涓婚」鐩椂锛屼富椤圭洰鍙互閫氳繃浠ヤ笅涓夌鏂瑰紡鎺ユ敹鏀粯鎴愬姛閫氱煡锛�
+ *
+ * 鏂瑰紡1锛氱洃鍚琒pring浜嬩欢锛堟帹鑽愶級
+ * 鏂瑰紡2锛氬疄鐜癙aymentCallback鎺ュ彛锛堟帹鑽愶級
+ * 鏂瑰紡3锛氭彁渚汬TTP鍥炶皟URL锛堢敤浜庤法鏈嶅姟璋冪敤锛�
+ *
+ * 娉ㄦ剰锛氭鏂囦欢浠呬綔涓虹ず渚嬶紝瀹為檯浣跨敤鏃跺簲鍦ㄤ富椤圭洰涓垱寤虹被浼肩殑瀹炵幇銆�
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@Component
+public class PaymentIntegrationExample implements PaymentCallback {
+
+ // ==================== 鏂瑰紡1锛氱洃鍚琒pring浜嬩欢 ====================
+
+ /**
+ * 鐩戝惉鏀粯鎴愬姛浜嬩欢
+ *
+ * 浼樼偣锛�
+ * - Spring鏍囧噯浜嬩欢鏈哄埗锛岃В鑰︽�уソ
+ * - 鍙互鏈夊涓洃鍚櫒
+ * - 鏀寔寮傛澶勭悊锛堟坊鍔燖Async锛�
+ *
+ * 浣跨敤鏂瑰紡锛�
+ * 鍦ㄤ富椤圭洰鐨勪换鎰廆Component绫讳腑娣诲姞姝ゆ柟娉�
+ */
+ @EventListener
+ public void handlePaymentSuccess(PaymentSuccessEvent event) {
+ PaymentOrder order = event.getOrder();
+ PaymentTransaction transaction = event.getTransaction();
+
+ log.info("銆愪簨浠剁洃鍚�戞敹鍒版敮浠樻垚鍔熼�氱煡");
+ log.info("涓氬姟璁㈠崟鍙�: {}", order.getBizOrderId());
+ log.info("鏀粯閲戦: {} 鍒�", order.getAmount());
+ log.info("鏀粯娓犻亾: {}", order.getChannel());
+ log.info("娓犻亾浜ゆ槗鍙�: {}", order.getChannelTradeNo());
+ log.info("鏀粯鏃堕棿: {}", order.getPaidAt());
+
+ // 澶勭悊涓氬姟閫昏緫
+ processOrderPaymentSuccess(order, transaction);
+ }
+
+ // ==================== 鏂瑰紡2锛氬疄鐜癙aymentCallback鎺ュ彛 ====================
+
+ /**
+ * 鏀粯鎴愬姛鍥炶皟
+ *
+ * 浼樼偣锛�
+ * - 鎺ュ彛绾︽潫锛屾槑纭洖璋冩柟娉�
+ * - 绫诲瀷瀹夊叏
+ * - IDE鏅鸿兘鎻愮ず
+ *
+ * 浣跨敤鏂瑰紡锛�
+ * 鍦ㄤ富椤圭洰涓垱寤虹被瀹炵幇PaymentCallback鎺ュ彛骞舵敞鍐屼负Bean
+ */
+ @Override
+ public void onPaymentSuccess(PaymentOrder order, PaymentTransaction transaction) {
+ log.info("銆愭帴鍙e洖璋冦�戞敹鍒版敮浠樻垚鍔熼�氱煡");
+ log.info("涓氬姟璁㈠崟鍙�: {}", order.getBizOrderId());
+ log.info("鏀粯閲戦: {} 鍒�", order.getAmount());
+
+ // 澶勭悊涓氬姟閫昏緫
+ processOrderPaymentSuccess(order, transaction);
+ }
+
+ // ==================== 鏂瑰紡3锛欻TTP鍥炶皟URL ====================
+
+ /**
+ * HTTP鍥炶皟鏂瑰紡
+ *
+ * 鍦ㄥ垱寤烘敮浠樻椂浼犲叆callbackUrl锛�
+ *
+ * PaymentRequest request = new PaymentRequest();
+ * request.setCallbackUrl("https://your-domain.com/api/payment/notify");
+ *
+ * 鐒跺悗鍦ㄤ富椤圭洰涓垱寤烘帴鏀禜TTP鍥炶皟鐨勬帴鍙o細
+ *
+ * @RestController
+ * @RequestMapping("/api/payment")
+ * public class PaymentNotifyController {
+ *
+ * @PostMapping("/notify")
+ * public String receiveNotify(@RequestBody String payload,
+ * @RequestHeader("X-Signature") String signature,
+ * @RequestHeader("X-Nonce") String nonce,
+ * @RequestHeader("X-Timestamp") String timestamp) {
+ * // 1. 楠岃瘉绛惧悕
+ * String signData = payload + nonce + timestamp;
+ * String calculated = SignUtil.hmacSha256(signData, "your-secret-key");
+ * if (!signature.equals(calculated)) {
+ * return "绛惧悕楠岃瘉澶辫触";
+ * }
+ *
+ * // 2. 瑙f瀽鏁版嵁
+ * JSONObject data = JSON.parseObject(payload);
+ * String bizOrderId = data.getString("bizOrderId");
+ * Integer amount = data.getInteger("amount");
+ *
+ * // 3. 澶勭悊涓氬姟閫昏緫
+ * processOrderPayment(bizOrderId, amount);
+ *
+ * return "success";
+ * }
+ * }
+ */
+
+ // ==================== 涓氬姟澶勭悊绀轰緥 ====================
+
+ /**
+ * 澶勭悊璁㈠崟鏀粯鎴愬姛鐨勪笟鍔¢�昏緫
+ */
+ private void processOrderPaymentSuccess(PaymentOrder order, PaymentTransaction transaction) {
+ String bizOrderId = order.getBizOrderId();
+
+ // 绀轰緥锛氭洿鏂颁笟鍔¤鍗曠姸鎬�
+ // orderService.updateOrderStatus(bizOrderId, "PAID");
+
+ // 绀轰緥锛氬彂閫佹敮浠樻垚鍔熼�氱煡
+ // notificationService.sendPaymentSuccessNotification(bizOrderId);
+
+ // 绀轰緥锛氳Е鍙戝悗缁笟鍔℃祦绋�
+ // fulfillmentService.startFulfillment(bizOrderId);
+
+ log.info("璁㈠崟 {} 鏀粯鎴愬姛锛屼笟鍔″鐞嗗畬鎴�", bizOrderId);
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/alipay/AlipayF2FClient.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/alipay/AlipayF2FClient.java
new file mode 100644
index 0000000..3b30836
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/alipay/AlipayF2FClient.java
@@ -0,0 +1,173 @@
+package com.ruoyi.payment.infrastructure.channel.alipay;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.request.AlipayTradePrecreateRequest;
+import com.alipay.api.request.AlipayTradeQueryRequest;
+import com.alipay.api.request.AlipayTradeCloseRequest;
+import com.alipay.api.response.AlipayTradePrecreateResponse;
+import com.alipay.api.response.AlipayTradeQueryResponse;
+import com.alipay.api.response.AlipayTradeCloseResponse;
+import com.ruoyi.payment.domain.model.GoodsDetail;
+import com.ruoyi.payment.domain.model.PaymentOrder;
+import com.ruoyi.payment.infrastructure.config.AlipayConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鏀粯瀹濆綋闈粯瀹㈡埛绔�
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@Component
+public class AlipayF2FClient {
+
+ @Autowired
+ private AlipayConfig alipayConfig;
+
+ private AlipayClient alipayClient;
+
+ /**
+ * 鍒濆鍖栨敮浠樺疂瀹㈡埛绔�
+ */
+ @PostConstruct
+ public void init() {
+ alipayClient = new DefaultAlipayClient(
+ alipayConfig.getServerUrl(),
+ alipayConfig.getAppId(),
+ alipayConfig.getPrivateKey(),
+ "json",
+ "UTF-8",
+ alipayConfig.getAlipayPublicKey(),
+ alipayConfig.getSignType()
+ );
+ log.info("鏀粯瀹濆鎴风鍒濆鍖栨垚鍔�");
+ }
+
+ /**
+ * 褰撻潰浠樹笅鍗� - 鐢熸垚浜岀淮鐮�
+ */
+ public String precreate(PaymentOrder order) throws AlipayApiException {
+ log.info("璋冪敤鏀粯瀹濆綋闈粯涓嬪崟鎺ュ彛锛岃鍗旾D: {}", order.getId());
+
+ AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
+ request.setNotifyUrl(alipayConfig.getNotifyUrl());
+
+ // 鏋勯�犱笟鍔″弬鏁�
+ StringBuilder bizContent = new StringBuilder();
+ bizContent.append("{");
+ bizContent.append("\"out_trade_no\":\"").append(order.getId()).append("\","); // 鍟嗘埛璁㈠崟鍙�
+ bizContent.append("\"total_amount\":\"").append(formatAmount(order.getAmount())).append("\","); // 璁㈠崟閲戦锛屽崟浣嶏細鍏�
+ bizContent.append("\"subject\":\"").append(order.getSubject()).append("\""); // 璁㈠崟鏍囬
+
+ // 娣诲姞鍟嗗搧鏄庣粏鍒楄〃
+ if (order.getGoodsDetails() != null && !order.getGoodsDetails().isEmpty()) {
+ bizContent.append(",\"goods_detail\":[");
+ for (int i = 0; i < order.getGoodsDetails().size(); i++) {
+ if (i > 0) {
+ bizContent.append(",");
+ }
+ bizContent.append("{");
+ bizContent.append("\"goods_id\":\"").append(order.getGoodsDetails().get(i).getGoodsId()).append("\",");
+ bizContent.append("\"goods_name\":\"").append(order.getGoodsDetails().get(i).getGoodsName()).append("\",");
+ bizContent.append("\"quantity\":").append(order.getGoodsDetails().get(i).getQuantity()).append(",");
+ bizContent.append("\"price\":\"").append(formatAmount(order.getGoodsDetails().get(i).getPrice())).append("\"");
+
+ // 娣诲姞鍙�夌殑鍟嗗搧绫诲埆鍜屾弿杩�
+ if (order.getGoodsDetails().get(i).getGoodsCategory() != null && !order.getGoodsDetails().get(i).getGoodsCategory().isEmpty()) {
+ bizContent.append(",\"goods_category\":\"").append(order.getGoodsDetails().get(i).getGoodsCategory()).append("\"");
+ }
+
+ if (order.getGoodsDetails().get(i).getBody() != null && !order.getGoodsDetails().get(i).getBody().isEmpty()) {
+ bizContent.append(",\"body\":\"").append(order.getGoodsDetails().get(i).getBody()).append("\"");
+ }
+
+ bizContent.append("}");
+ }
+ bizContent.append("]");
+ }
+
+ if (order.getDescription() != null && !order.getDescription().isEmpty()) {
+ bizContent.append(",\"body\":\"").append(order.getDescription()).append("\"");
+ }
+
+ // 璁剧疆瓒呮椂鏃堕棿
+ bizContent.append(",\"timeout_express\":\"30m\"");
+
+ bizContent.append("}");
+
+ request.setBizContent(bizContent.toString());
+ log.info("鏀粯瀹濆綋闈粯璇锋眰鍙傛暟: {}", bizContent.toString());
+
+ // 璋冪敤SDK
+ AlipayTradePrecreateResponse response = alipayClient.execute(request);
+
+ if (!response.isSuccess()) {
+ log.error("鏀粯瀹濆綋闈粯涓嬪崟澶辫触: {}, {}", response.getSubCode(), response.getSubMsg());
+ throw new AlipayApiException("鏀粯瀹濆綋闈粯涓嬪崟澶辫触: " + response.getSubMsg());
+ }
+
+ String qrCode = response.getQrCode();
+ if (qrCode == null || qrCode.isEmpty()) {
+ throw new AlipayApiException("鏀粯瀹濇湭杩斿洖浜岀淮鐮�");
+ }
+
+ log.info("鏀粯瀹濆綋闈粯涓嬪崟鎴愬姛锛宷rCode: {}", qrCode);
+ return qrCode;
+ }
+
+ /**
+ * 鏌ヨ璁㈠崟
+ */
+ public AlipayTradeQueryResponse queryOrder(String outTradeNo) throws AlipayApiException {
+ log.info("鏌ヨ鏀粯瀹濊鍗曪紝鍟嗘埛璁㈠崟鍙�: {}", outTradeNo);
+
+ AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
+ request.setBizContent("{\"out_trade_no\":\"" + outTradeNo + "\"}");
+
+ AlipayTradeQueryResponse response = alipayClient.execute(request);
+
+ if (!response.isSuccess()) {
+ log.error("鏌ヨ鏀粯瀹濊鍗曞け璐�: {}, {}", response.getSubCode(), response.getSubMsg());
+ throw new AlipayApiException("鏌ヨ璁㈠崟澶辫触: " + response.getSubMsg());
+ }
+
+ log.info("鏌ヨ鏀粯瀹濊鍗曟垚鍔燂紝浜ゆ槗鐘舵��: {}", response.getTradeStatus());
+ return response;
+ }
+
+ /**
+ * 鍏抽棴璁㈠崟
+ */
+ public void closeOrder(String outTradeNo) throws AlipayApiException {
+ log.info("鍏抽棴鏀粯瀹濊鍗曪紝鍟嗘埛璁㈠崟鍙�: {}", outTradeNo);
+
+ AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
+ request.setBizContent("{\"out_trade_no\":\"" + outTradeNo + "\"}");
+
+ AlipayTradeCloseResponse response = alipayClient.execute(request);
+
+ if (!response.isSuccess()) {
+ log.error("鍏抽棴鏀粯瀹濊鍗曞け璐�: {}, {}", response.getSubCode(), response.getSubMsg());
+ throw new AlipayApiException("鍏抽棴璁㈠崟澶辫触: " + response.getSubMsg());
+ }
+
+ log.info("鍏抽棴鏀粯瀹濊鍗曟垚鍔�");
+ }
+
+ /**
+ * 鏍煎紡鍖栭噾棰濓細鍒嗚浆鍏冿紝淇濈暀涓や綅灏忔暟
+ */
+ private String formatAmount(Integer amountInCents) {
+ BigDecimal amount = new BigDecimal(amountInCents).divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP);
+ return amount.toString();
+ }
+}
\ No newline at end of file
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/alipay/AlipayThirdPartyClient.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/alipay/AlipayThirdPartyClient.java
new file mode 100644
index 0000000..81c1363
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/alipay/AlipayThirdPartyClient.java
@@ -0,0 +1,196 @@
+package com.ruoyi.payment.infrastructure.channel.alipay;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鏀粯瀹濈涓夋柟鎺ュ彛瀹㈡埛绔�
+ * 璋冪敤鏃х郴缁熺殑鏀粯瀹濆綋闈粯鎺ュ彛
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@Component
+public class AlipayThirdPartyClient {
+
+ /**
+ * 绗笁鏂规敮浠樺疂褰撻潰浠樻帴鍙e湴鍧�
+ */
+ private static final String THIRD_PARTY_ALIPAY_URL = "https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php";
+
+ /**
+ * 绗笁鏂规敮浠樺疂鏌ヨ鎺ュ彛鍦板潃
+ */
+ private static final String THIRD_PARTY_QUERY_URL = "https://sys.966120.com.cn/alipay_pay_query.php";
+
+ /**
+ * 璋冪敤绗笁鏂规帴鍙g敓鎴愭敮浠樺疂褰撻潰浠楿RL
+ *
+ * @param notifyUrl 寮傛鍥炶皟閫氱煡鍦板潃
+ * @param outTradeNo 鍟嗘埛璁㈠崟鍙�
+ * @param totalFee 璁㈠崟閲戦锛堝崟浣嶏細鍒嗭級
+ * @param serviceOrdId 涓氬姟璁㈠崟ID
+ * @return 鏀粯瀹濆綋闈粯URL
+ * @throws Exception 璋冪敤寮傚父
+ */
+ public String createQrCodeUrl(String notifyUrl, String outTradeNo, Integer totalFee, String serviceOrdId)
+ throws Exception {
+ log.info("璋冪敤绗笁鏂规敮浠樺疂褰撻潰浠樻帴鍙o紝璁㈠崟鍙�: {}, 閲戦: {}鍒�, 涓氬姟璁㈠崟ID: {}",
+ outTradeNo, totalFee, serviceOrdId);
+
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(THIRD_PARTY_ALIPAY_URL);
+
+ // 璁剧疆Cookie澶�
+ httpPost.setHeader("Cookie", "CAMEName=");
+
+ // 鏋勫缓multipart/form-data璇锋眰浣�
+ HttpEntity entity = MultipartEntityBuilder.create()
+ .addTextBody("notify_url", notifyUrl)
+ .addTextBody("out_trade_no", outTradeNo)
+ .addTextBody("total_fee", String.valueOf(totalFee))
+ .addTextBody("ServiceOrdID", serviceOrdId)
+ .build();
+
+ httpPost.setEntity(entity);
+
+ log.info("鍙戦�佽姹傚埌绗笁鏂规帴鍙�: {}", THIRD_PARTY_ALIPAY_URL);
+
+ // 鍙戦�佽姹�
+ try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+ int statusCode = response.getStatusLine().getStatusCode();
+ String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
+
+ log.info("绗笁鏂规帴鍙e搷搴旓紝鐘舵�佺爜: {}, 鍝嶅簲鍐呭: {}", statusCode, responseBody);
+
+ if (statusCode >= 200 && statusCode < 300) {
+ // 鍝嶅簲鎴愬姛锛岃В鏋愯繑鍥炵殑URL
+ String qrCodeUrl = parseQrCodeUrl(responseBody);
+
+ if (qrCodeUrl == null || qrCodeUrl.isEmpty()) {
+ throw new Exception("绗笁鏂规帴鍙f湭杩斿洖鏈夋晥鐨勬敮浠楿RL锛屽搷搴�: " + responseBody);
+ }
+
+ log.info("绗笁鏂规敮浠樺疂褰撻潰浠楿RL鐢熸垚鎴愬姛: {}", qrCodeUrl);
+ return qrCodeUrl;
+ } else {
+ throw new Exception("绗笁鏂规帴鍙h皟鐢ㄥけ璐ワ紝鐘舵�佺爜: " + statusCode + ", 鍝嶅簲: " + responseBody);
+ }
+ }
+ } catch (Exception e) {
+ log.error("璋冪敤绗笁鏂规敮浠樺疂褰撻潰浠樻帴鍙eけ璐�", e);
+ throw new Exception("璋冪敤绗笁鏂规敮浠樺疂褰撻潰浠樻帴鍙eけ璐�: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 瑙f瀽绗笁鏂规帴鍙e搷搴旓紝鎻愬彇鏀粯URL
+ * 鏍规嵁瀹為檯杩斿洖鏍煎紡璋冩暣瑙f瀽閫昏緫
+ *
+ * @param responseBody 鍝嶅簲鍐呭
+ * @return 鏀粯URL
+ */
+ private String parseQrCodeUrl(String responseBody) {
+ // TODO: 鏍规嵁瀹為檯杩斿洖鏍煎紡璋冩暣瑙f瀽閫昏緫
+ // 杩欓噷鍋囪鐩存帴杩斿洖URL瀛楃涓诧紝鎴栬�呰繑鍥濲SON鏍煎紡
+
+ if (responseBody == null || responseBody.isEmpty()) {
+ return null;
+ }
+
+ // 鍘婚櫎鍙兘鐨勫紩鍙峰拰绌虹櫧瀛楃
+ String url = responseBody.trim();
+ if (url.startsWith("\"") && url.endsWith("\"")) {
+ url = url.substring(1, url.length() - 1);
+ }
+
+ // 濡傛灉鏄疛SON鏍煎紡锛屽彲浠ヤ娇鐢‵astJSON瑙f瀽
+ // 绀轰緥锛歿"status": "success", "qr_code": "https://..."}
+ // JSONObject json = JSON.parseObject(responseBody);
+ // return json.getString("qr_code");
+
+ return url;
+ }
+
+ /**
+ * 鏌ヨ鏀粯瀹濅氦鏄撶姸鎬�
+ *
+ * @param outTradeNo 鍟嗘埛璁㈠崟鍙�
+ * @return 浜ゆ槗鐘舵�侊紙SUCCESS-鎴愬姛锛屽叾浠�-澶辫触鎴栧鐞嗕腑锛�
+ * @throws Exception 璋冪敤寮傚父
+ */
+ public String queryTradeStatus(String outTradeNo) throws Exception {
+ log.info("璋冪敤绗笁鏂规敮浠樺疂鏌ヨ鎺ュ彛锛岃鍗曞彿: {}", outTradeNo);
+
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(THIRD_PARTY_QUERY_URL);
+
+ // 璁剧疆Cookie澶�
+ httpPost.setHeader("Cookie", "CAMEName=");
+
+ // 鏋勫缓multipart/form-data璇锋眰浣�
+ HttpEntity entity = MultipartEntityBuilder.create()
+ .addTextBody("out_trade_no", outTradeNo)
+ .build();
+
+ httpPost.setEntity(entity);
+
+ log.info("鍙戦�佹煡璇㈣姹傚埌绗笁鏂规帴鍙�: {}", THIRD_PARTY_QUERY_URL);
+
+ // 鍙戦�佽姹�
+ try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+ int statusCode = response.getStatusLine().getStatusCode();
+ String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
+
+ log.info("绗笁鏂规煡璇㈡帴鍙e搷搴旓紝鐘舵�佺爜: {}, 鍝嶅簲鍐呭: {}", statusCode, responseBody);
+
+ if (statusCode >= 200 && statusCode < 300) {
+ // 鍝嶅簲鎴愬姛锛岃В鏋愪氦鏄撶姸鎬�
+ String tradeStatus = parseTradeStatus(responseBody);
+
+ log.info("绗笁鏂规敮浠樺疂浜ゆ槗鏌ヨ鎴愬姛锛岃鍗曞彿: {}, 鐘舵��: {}", outTradeNo, tradeStatus);
+ return tradeStatus;
+ } else {
+ throw new Exception("绗笁鏂规煡璇㈡帴鍙h皟鐢ㄥけ璐ワ紝鐘舵�佺爜: " + statusCode + ", 鍝嶅簲: " + responseBody);
+ }
+ }
+ } catch (Exception e) {
+ log.error("璋冪敤绗笁鏂规敮浠樺疂鏌ヨ鎺ュ彛澶辫触", e);
+ throw new Exception("璋冪敤绗笁鏂规敮浠樺疂鏌ヨ鎺ュ彛澶辫触: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 瑙f瀽浜ゆ槗鐘舵��
+ *
+ * @param responseBody 鍝嶅簲鍐呭
+ * @return 浜ゆ槗鐘舵��
+ */
+ private String parseTradeStatus(String responseBody) {
+ if (responseBody == null || responseBody.isEmpty()) {
+ return "UNKNOWN";
+ }
+
+ // 鍘婚櫎绌虹櫧瀛楃
+ String status = responseBody.trim();
+
+ // 鍘婚櫎鍙兘鐨勫紩鍙�
+ if (status.startsWith("\"") && status.endsWith("\"")) {
+ status = status.substring(1, status.length() - 1);
+ }
+
+ // 杞崲涓哄ぇ鍐�
+ status = status.toUpperCase();
+
+ log.info("瑙f瀽浜ゆ槗鐘舵��: {}", status);
+
+ return status;
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/alipay/AlipayUtil.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/alipay/AlipayUtil.java
new file mode 100644
index 0000000..4483a4b
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/alipay/AlipayUtil.java
@@ -0,0 +1,43 @@
+package com.ruoyi.payment.infrastructure.channel.alipay;
+
+import com.alipay.api.internal.util.AlipaySignature;
+import com.ruoyi.payment.infrastructure.config.AlipayConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * 鏀粯瀹濆伐鍏风被
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@Component
+public class AlipayUtil {
+
+ @Autowired
+ private AlipayConfig alipayConfig;
+
+ /**
+ * 楠岃瘉鏀粯瀹濆洖璋冪鍚�
+ */
+ public boolean verifySign(Map<String, String> params) {
+ try {
+ if(!alipayConfig.getCheckSign()){
+ log.info("寮�濮嬩笉楠岃瘉鍥炶皟绛惧悕");
+ return true;
+ }
+ return AlipaySignature.rsaCheckV1(
+ params,
+ alipayConfig.getAlipayPublicKey(),
+ "UTF-8",
+ alipayConfig.getSignType()
+ );
+ } catch (Exception e) {
+ log.error("鏀粯瀹濈鍚嶉獙璇佸け璐�", e);
+ return false;
+ }
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/wechat/WxPayUtil.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/wechat/WxPayUtil.java
new file mode 100644
index 0000000..fc386ad
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/wechat/WxPayUtil.java
@@ -0,0 +1,129 @@
+package com.ruoyi.payment.infrastructure.channel.wechat;
+
+import com.ruoyi.payment.infrastructure.config.WechatPayConfig;
+import com.ruoyi.payment.infrastructure.util.SignUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.*;
+
+/**
+ * 寰俊鏀粯宸ュ叿绫�
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@Component
+public class WxPayUtil {
+
+ @Autowired
+ private WechatPayConfig wechatPayConfig;
+
+ /**
+ * XML杞琈ap
+ */
+ public Map<String, String> xmlToMap(String xml) throws Exception {
+ Map<String, String> data = new HashMap<>();
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ InputStream is = new ByteArrayInputStream(xml.getBytes("UTF-8"));
+ Document doc = builder.parse(is);
+ doc.getDocumentElement().normalize();
+ NodeList nodeList = doc.getDocumentElement().getChildNodes();
+
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ Node node = nodeList.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ data.put(node.getNodeName(), node.getTextContent());
+ }
+ }
+ return data;
+ }
+
+ /**
+ * Map杞琗ML
+ */
+ public String mapToXml(Map<String, String> data) {
+ StringBuilder xml = new StringBuilder("<xml>");
+ for (Map.Entry<String, String> entry : data.entrySet()) {
+ xml.append("<").append(entry.getKey()).append(">");
+ xml.append("<![CDATA[").append(entry.getValue()).append("]]>");
+ xml.append("</").append(entry.getKey()).append(">");
+ }
+ xml.append("</xml>");
+ return xml.toString();
+ }
+
+ /**
+ * 鐢熸垚绛惧悕
+ */
+ public String generateSign(Map<String, String> data) {
+ // 1. 鍙傛暟鍚岮SCII鐮佷粠灏忓埌澶ф帓搴�
+ Set<String> keySet = data.keySet();
+ String[] keyArray = keySet.toArray(new String[0]);
+ Arrays.sort(keyArray);
+
+ // 2. 鎷兼帴鍙傛暟
+ StringBuilder sb = new StringBuilder();
+ for (String k : keyArray) {
+ if ("sign".equals(k)) {
+ continue;
+ }
+ String value = data.get(k);
+ if (value != null && value.trim().length() > 0) {
+ sb.append(k).append("=").append(value).append("&");
+ }
+ }
+
+ // 3. 鎷兼帴瀵嗛挜
+ sb.append("key=").append(wechatPayConfig.getMchKey());
+
+ // 4. MD5鍔犲瘑骞惰浆澶у啓
+ return SignUtil.md5(sb.toString());
+ }
+
+ /**
+ * 楠岃瘉绛惧悕
+ */
+ public boolean verifySign(Map<String, String> data) {
+ if(!wechatPayConfig.getCheckSign()){
+ log.info("寰俊鍥炶皟涓嶆娴嬮獙璇佺鍚�");
+ return true;
+ }
+ String sign = data.get("sign");
+ if (sign == null || sign.isEmpty()) {
+ return false;
+ }
+
+ String calculatedSign = generateSign(data);
+ return sign.equals(calculatedSign);
+ }
+
+ /**
+ * 鏋勯�犳垚鍔熷簲绛�
+ */
+ public String buildSuccessResponse() {
+ Map<String, String> response = new HashMap<>();
+ response.put("return_code", "SUCCESS");
+ response.put("return_msg", "OK");
+ return mapToXml(response);
+ }
+
+ /**
+ * 鏋勯�犲け璐ュ簲绛�
+ */
+ public String buildFailResponse(String msg) {
+ Map<String, String> response = new HashMap<>();
+ response.put("return_code", "FAIL");
+ response.put("return_msg", msg);
+ return mapToXml(response);
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/wechat/WxPayV2Client.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/wechat/WxPayV2Client.java
new file mode 100644
index 0000000..238f0d0
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/channel/wechat/WxPayV2Client.java
@@ -0,0 +1,179 @@
+package com.ruoyi.payment.infrastructure.channel.wechat;
+
+import com.ruoyi.payment.domain.model.PaymentOrder;
+import com.ruoyi.payment.infrastructure.config.WechatPayConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 寰俊鏀粯v2瀹㈡埛绔�
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@Component
+public class WxPayV2Client {
+
+ private static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
+ private static final String ORDER_QUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
+ private static final String CLOSE_ORDER_URL = "https://api.mch.weixin.qq.com/pay/closeorder";
+
+ @Autowired
+ private WechatPayConfig wechatPayConfig;
+
+ @Autowired
+ private WxPayUtil wxPayUtil;
+
+ /**
+ * 缁熶竴涓嬪崟 - Native鏀粯
+ */
+ public String unifiedOrder(PaymentOrder order) throws Exception {
+ log.info("璋冪敤寰俊缁熶竴涓嬪崟鎺ュ彛锛岃鍗旾D: {}", order.getId());
+
+ // 鏋勯�犺姹傚弬鏁�
+ Map<String, String> params = new HashMap<>();
+ params.put("appid", wechatPayConfig.getAppId());
+ params.put("mch_id", wechatPayConfig.getMchId());
+ params.put("nonce_str", generateNonceStr());
+ params.put("body", order.getSubject());
+ params.put("out_trade_no", String.valueOf(order.getId())); // 浣跨敤璁㈠崟ID浣滀负鍟嗘埛璁㈠崟鍙�
+ params.put("total_fee", String.valueOf(order.getAmount())); // 鍗曚綅锛氬垎
+ params.put("spbill_create_ip", "127.0.0.1");
+ params.put("notify_url", wechatPayConfig.getNotifyUrl());
+ params.put("trade_type", "NATIVE");
+
+ if (order.getDescription() != null && !order.getDescription().isEmpty()) {
+ params.put("detail", order.getDescription());
+ }
+
+ // 鐢熸垚绛惧悕
+ String sign = wxPayUtil.generateSign(params);
+ params.put("sign", sign);
+
+ // 杞崲涓篨ML
+ String requestXml = wxPayUtil.mapToXml(params);
+ log.info("寰俊缁熶竴涓嬪崟璇锋眰: {}", requestXml);
+
+ // 鍙戦�丠TTP璇锋眰
+ String responseXml = doPost(UNIFIED_ORDER_URL, requestXml);
+ log.info("寰俊缁熶竴涓嬪崟鍝嶅簲: {}", responseXml);
+
+ // 瑙f瀽鍝嶅簲
+ Map<String, String> response = wxPayUtil.xmlToMap(responseXml);
+
+ // 楠岃瘉鍝嶅簲绛惧悕
+ if (!wxPayUtil.verifySign(response)) {
+ throw new RuntimeException("寰俊杩斿洖绛惧悕楠岃瘉澶辫触");
+ }
+
+ // 妫�鏌ヨ繑鍥炵姸鎬�
+ if (!"SUCCESS".equals(response.get("return_code"))) {
+ throw new RuntimeException("寰俊缁熶竴涓嬪崟澶辫触: " + response.get("return_msg"));
+ }
+
+ if (!"SUCCESS".equals(response.get("result_code"))) {
+ throw new RuntimeException("寰俊缁熶竴涓嬪崟涓氬姟澶辫触: " + response.get("err_code_des"));
+ }
+
+ // 杩斿洖code_url
+ String codeUrl = response.get("code_url");
+ if (codeUrl == null || codeUrl.isEmpty()) {
+ throw new RuntimeException("寰俊鏈繑鍥瀋ode_url");
+ }
+
+ log.info("寰俊缁熶竴涓嬪崟鎴愬姛锛宑ode_url: {}", codeUrl);
+ return codeUrl;
+ }
+
+ /**
+ * 鏌ヨ璁㈠崟
+ */
+ public Map<String, String> queryOrder(String outTradeNo) throws Exception {
+ log.info("鏌ヨ寰俊璁㈠崟锛屽晢鎴疯鍗曞彿: {}", outTradeNo);
+
+ Map<String, String> params = new HashMap<>();
+ params.put("appid", wechatPayConfig.getAppId());
+ params.put("mch_id", wechatPayConfig.getMchId());
+ params.put("out_trade_no", outTradeNo);
+ params.put("nonce_str", generateNonceStr());
+
+ String sign = wxPayUtil.generateSign(params);
+ params.put("sign", sign);
+
+ String requestXml = wxPayUtil.mapToXml(params);
+ String responseXml = doPost(ORDER_QUERY_URL, requestXml);
+
+ Map<String, String> response = wxPayUtil.xmlToMap(responseXml);
+
+ if (!wxPayUtil.verifySign(response)) {
+ throw new RuntimeException("鏌ヨ璁㈠崟鍝嶅簲绛惧悕楠岃瘉澶辫触");
+ }
+
+ if (!"SUCCESS".equals(response.get("return_code"))) {
+ throw new RuntimeException("鏌ヨ璁㈠崟澶辫触: " + response.get("return_msg"));
+ }
+
+ return response;
+ }
+
+ /**
+ * 鍏抽棴璁㈠崟
+ */
+ public void closeOrder(String outTradeNo) throws Exception {
+ log.info("鍏抽棴寰俊璁㈠崟锛屽晢鎴疯鍗曞彿: {}", outTradeNo);
+
+ Map<String, String> params = new HashMap<>();
+ params.put("appid", wechatPayConfig.getAppId());
+ params.put("mch_id", wechatPayConfig.getMchId());
+ params.put("out_trade_no", outTradeNo);
+ params.put("nonce_str", generateNonceStr());
+
+ String sign = wxPayUtil.generateSign(params);
+ params.put("sign", sign);
+
+ String requestXml = wxPayUtil.mapToXml(params);
+ String responseXml = doPost(CLOSE_ORDER_URL, requestXml);
+
+ Map<String, String> response = wxPayUtil.xmlToMap(responseXml);
+
+ if (!"SUCCESS".equals(response.get("return_code"))) {
+ throw new RuntimeException("鍏抽棴璁㈠崟澶辫触: " + response.get("return_msg"));
+ }
+
+ log.info("鍏抽棴寰俊璁㈠崟鎴愬姛");
+ }
+
+ /**
+ * 鍙戦�丳OST璇锋眰
+ */
+ private String doPost(String url, String xmlData) throws Exception {
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setHeader("Content-Type", "text/xml;charset=UTF-8");
+ httpPost.setEntity(new StringEntity(xmlData, "UTF-8"));
+
+ try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+ HttpEntity entity = response.getEntity();
+ return EntityUtils.toString(entity, "UTF-8");
+ }
+ }
+ }
+
+ /**
+ * 鐢熸垚闅忔満瀛楃涓�
+ */
+ private String generateNonceStr() {
+ return java.util.UUID.randomUUID().toString().replace("-", "");
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/AlipayConfig.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/AlipayConfig.java
new file mode 100644
index 0000000..1a22b6f
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/AlipayConfig.java
@@ -0,0 +1,55 @@
+package com.ruoyi.payment.infrastructure.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鏀粯瀹濋厤缃�
+ *
+ * @author ruoyi
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "payment.alipay")
+public class AlipayConfig {
+
+ /** 搴旂敤ID */
+ private String appId;
+
+ /** 鍟嗘埛绉侀挜 */
+ private String privateKey;
+
+ /** 鏀粯瀹濆叕閽� */
+ private String alipayPublicKey;
+
+ /** 鏈嶅姟鍣ㄥ湴鍧� */
+ private String serverUrl;
+
+ /** 鍥炶皟鍦板潃 */
+ private String notifyUrl;
+
+ /** 鍦ㄥ洖璋冩椂 鏄惁妫�鏌ョ鍚� */
+ private Boolean checkSign = true;
+
+ /** 绛惧悕绫诲瀷 */
+ private String signType = "RSA2";
+
+ /** 鏀粯鏂瑰紡: OFFICIAL(瀹樻柟) 鎴� THIRD_PARTY(绗笁鏂�) */
+ private String paymentMethod = "OFFICIAL";
+
+ /** 绗笁鏂规敮浠橀厤缃� */
+ private ThirdPartyConfig thirdParty = new ThirdPartyConfig();
+
+ @Data
+ public static class ThirdPartyConfig {
+ /** 鏄惁鍚敤 */
+ private boolean enabled = false;
+ /** 绗笁鏂规帴鍙RL */
+ private String url = "https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php";
+ /** 榛樿鍥炶皟鍦板潃 */
+ private String defaultNotifyUrl = "https://dsp.966120.com.cn/alipay/pay_notify";
+ /** 瓒呮椂鏃堕棿锛堟绉掞級 */
+ private int timeout = 30000;
+ }
+}
\ No newline at end of file
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/BusinessCallbackConfig.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/BusinessCallbackConfig.java
new file mode 100644
index 0000000..208973c
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/BusinessCallbackConfig.java
@@ -0,0 +1,37 @@
+package com.ruoyi.payment.infrastructure.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 涓氬姟鍥炶皟閰嶇疆
+ *
+ * @author ruoyi
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "payment.business")
+public class BusinessCallbackConfig {
+
+ /** 鍥炶皟绛惧悕瀵嗛挜 */
+ private String callbackSignSecret;
+
+ /** 鏈�澶ч噸璇曟鏁� */
+ private Integer callbackRetryMaxCount = 10;
+
+ /** 閲嶈瘯闂撮殧锛堝垎閽燂級 */
+ private String callbackRetryIntervals = "0,1,5,15,60";
+
+ /**
+ * 鑾峰彇閲嶈瘯闂撮殧鏁扮粍
+ */
+ public int[] getRetryIntervalsArray() {
+ String[] parts = callbackRetryIntervals.split(",");
+ int[] intervals = new int[parts.length];
+ for (int i = 0; i < parts.length; i++) {
+ intervals[i] = Integer.parseInt(parts[i].trim());
+ }
+ return intervals;
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/QrCodeConfig.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/QrCodeConfig.java
new file mode 100644
index 0000000..f396c9f
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/QrCodeConfig.java
@@ -0,0 +1,22 @@
+package com.ruoyi.payment.infrastructure.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 浜岀淮鐮侀厤缃�
+ *
+ * @author ruoyi
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "payment.qrcode")
+public class QrCodeConfig {
+
+ /** 浜岀淮鐮佸昂瀵� */
+ private Integer size = 300;
+
+ /** 鍥剧墖鏍煎紡 */
+ private String format = "PNG";
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/WechatPayConfig.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/WechatPayConfig.java
new file mode 100644
index 0000000..7232142
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/config/WechatPayConfig.java
@@ -0,0 +1,36 @@
+package com.ruoyi.payment.infrastructure.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 寰俊鏀粯閰嶇疆
+ *
+ * @author ruoyi
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "payment.wechat")
+public class WechatPayConfig {
+
+ /** 搴旂敤ID */
+ private String appId;
+
+ /** 鍟嗘埛鍙� */
+ private String mchId;
+
+ /** 鍟嗘埛瀵嗛挜 */
+ private String mchKey;
+
+ /** 鍥炶皟鍦板潃 */
+ private String notifyUrl;
+
+ /**
+ * 鍥炶皟鏄惁妫�鏌ョ鍚�
+ */
+ private Boolean checkSign=true;
+
+ /** 绛惧悕绫诲瀷 */
+ private String signType = "MD5";
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/BizCallbackLogMapper.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/BizCallbackLogMapper.java
new file mode 100644
index 0000000..43e290a
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/BizCallbackLogMapper.java
@@ -0,0 +1,29 @@
+package com.ruoyi.payment.infrastructure.persistence.mapper;
+
+import com.ruoyi.payment.domain.model.BizCallbackLog;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 涓氬姟鍥炶皟鏃ュ織Mapper
+ *
+ * @author ruoyi
+ */
+@Mapper
+public interface BizCallbackLogMapper {
+
+ /**
+ * 鎻掑叆涓氬姟鍥炶皟鏃ュ織
+ */
+ int insert(BizCallbackLog bizCallbackLog);
+
+ /**
+ * 鏇存柊涓氬姟鍥炶皟鏃ュ織
+ */
+ int update(BizCallbackLog bizCallbackLog);
+
+ /**
+ * 鏍规嵁ID鏌ヨ
+ */
+ BizCallbackLog selectById(@Param("id") Long id);
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/NotifyLogMapper.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/NotifyLogMapper.java
new file mode 100644
index 0000000..d5dd215
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/NotifyLogMapper.java
@@ -0,0 +1,35 @@
+package com.ruoyi.payment.infrastructure.persistence.mapper;
+
+import com.ruoyi.payment.domain.model.NotifyLog;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 娓犻亾鍥炶皟鏃ュ織Mapper
+ *
+ * @author ruoyi
+ */
+@Mapper
+public interface NotifyLogMapper {
+
+ /**
+ * 鎻掑叆鍥炶皟鏃ュ織
+ */
+ int insert(NotifyLog notifyLog);
+
+ /**
+ * 鏇存柊鍥炶皟鏃ュ織
+ */
+ int update(NotifyLog notifyLog);
+
+ /**
+ * 鏍规嵁ID鏌ヨ
+ */
+ NotifyLog selectById(@Param("id") Long id);
+
+ /**
+ * 鏌ヨ宸插鐞嗙殑鍥炶皟璁板綍鏁伴噺锛堝箓绛夋�ф鏌ワ級
+ */
+ int selectProcessedCount(@Param("channel") String channel,
+ @Param("notifyIdOrSerial") String notifyIdOrSerial);
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/OperationAuditMapper.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/OperationAuditMapper.java
new file mode 100644
index 0000000..3036aa5
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/OperationAuditMapper.java
@@ -0,0 +1,24 @@
+package com.ruoyi.payment.infrastructure.persistence.mapper;
+
+import com.ruoyi.payment.domain.model.OperationAudit;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 鎿嶄綔瀹¤Mapper
+ *
+ * @author ruoyi
+ */
+@Mapper
+public interface OperationAuditMapper {
+
+ /**
+ * 鎻掑叆瀹¤璁板綍
+ */
+ int insert(OperationAudit operationAudit);
+
+ /**
+ * 鏍规嵁ID鏌ヨ
+ */
+ OperationAudit selectById(@Param("id") Long id);
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/PaymentOrderMapper.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/PaymentOrderMapper.java
new file mode 100644
index 0000000..693fc35
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/PaymentOrderMapper.java
@@ -0,0 +1,40 @@
+package com.ruoyi.payment.infrastructure.persistence.mapper;
+
+import com.ruoyi.payment.domain.model.PaymentOrder;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 鏀粯璁㈠崟Mapper
+ *
+ * @author ruoyi
+ */
+@Mapper
+public interface PaymentOrderMapper {
+
+ /**
+ * 鎻掑叆鏀粯璁㈠崟
+ */
+ int insert(PaymentOrder order);
+
+ /**
+ * 鏇存柊鏀粯璁㈠崟
+ */
+ int update(PaymentOrder order);
+
+ /**
+ * 鏍规嵁ID鏌ヨ璁㈠崟
+ */
+ PaymentOrder selectById(@Param("id") Long id);
+
+ /**
+ * 鏍规嵁涓氬姟璁㈠崟鍙峰拰娓犻亾鏌ヨ璁㈠崟
+ */
+ PaymentOrder selectByBizOrderIdAndChannel(@Param("bizOrderId") String bizOrderId,
+ @Param("channel") String channel);
+
+ /**
+ * 鏍规嵁娓犻亾浜ゆ槗鍙锋煡璇㈣鍗�
+ */
+ PaymentOrder selectByChannelTradeNo(@Param("channelTradeNo") String channelTradeNo);
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/PaymentTransactionMapper.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/PaymentTransactionMapper.java
new file mode 100644
index 0000000..264f242
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/persistence/mapper/PaymentTransactionMapper.java
@@ -0,0 +1,44 @@
+package com.ruoyi.payment.infrastructure.persistence.mapper;
+
+import com.ruoyi.payment.domain.model.PaymentTransaction;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 鏀粯浜ゆ槗Mapper
+ *
+ * @author ruoyi
+ */
+@Mapper
+public interface PaymentTransactionMapper {
+
+ /**
+ * 鎻掑叆浜ゆ槗璁板綍
+ */
+ int insert(PaymentTransaction transaction);
+
+ /**
+ * 鏇存柊浜ゆ槗璁板綍
+ */
+ int update(PaymentTransaction transaction);
+
+ /**
+ * 鏍规嵁ID鏌ヨ浜ゆ槗
+ */
+ PaymentTransaction selectById(@Param("id") Long id);
+
+ /**
+ * 鏌ヨ璁㈠崟鐨勫緟鏀粯浜ゆ槗
+ */
+ PaymentTransaction selectPendingByOrderId(@Param("orderId") Long orderId);
+
+ /**
+ * 鏍规嵁娓犻亾浜ゆ槗鍙锋煡璇�
+ */
+ PaymentTransaction selectByChannelTradeNo(@Param("channelTradeNo") String channelTradeNo);
+
+ /**
+ * 鏌ヨ璁㈠崟鐨勬渶鏂颁氦鏄�
+ */
+ PaymentTransaction selectLatestByOrderId(@Param("orderId") Long orderId);
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/util/QrCodeUtil.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/util/QrCodeUtil.java
new file mode 100644
index 0000000..e3df5b3
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/util/QrCodeUtil.java
@@ -0,0 +1,61 @@
+package com.ruoyi.payment.infrastructure.util;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+import org.springframework.stereotype.Component;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 浜岀淮鐮佺敓鎴愬伐鍏�
+ *
+ * @author ruoyi
+ */
+@Component
+public class QrCodeUtil {
+
+ /**
+ * 鐢熸垚浜岀淮鐮丅ase64瀛楃涓�
+ *
+ * @param content 浜岀淮鐮佸唴瀹�
+ * @param size 灏哄
+ * @return Base64瀛楃涓�
+ */
+ public String generateQrCodeBase64(String content, int size) {
+ try {
+ // 閰嶇疆鍙傛暟
+ Map<EncodeHintType, Object> hints = new HashMap<>();
+ hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
+ hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
+ hints.put(EncodeHintType.MARGIN, 1);
+
+ // 鐢熸垚浜岀淮鐮�
+ QRCodeWriter qrCodeWriter = new QRCodeWriter();
+ BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, size, size, hints);
+
+ // 杞崲涓哄浘鐗�
+ BufferedImage bufferedImage = MatrixToImageWriter.toBufferedImage(bitMatrix);
+
+ // 杞崲涓築ase64
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ImageIO.write(bufferedImage, "PNG", baos);
+ byte[] bytes = baos.toByteArray();
+ String base64String = Base64.getEncoder().encodeToString(bytes);
+
+ return "data:image/png;base64," + base64String;
+ } catch (WriterException | IOException e) {
+ throw new RuntimeException("浜岀淮鐮佺敓鎴愬け璐�", e);
+ }
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/util/SignUtil.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/util/SignUtil.java
new file mode 100644
index 0000000..69118d3
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/util/SignUtil.java
@@ -0,0 +1,63 @@
+package com.ruoyi.payment.infrastructure.util;
+
+import org.apache.commons.codec.digest.DigestUtils;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+
+/**
+ * 绛惧悕宸ュ叿绫�
+ *
+ * @author ruoyi
+ */
+public class SignUtil {
+
+ /**
+ * MD5绛惧悕
+ *
+ * @param data 寰呯鍚嶆暟鎹�
+ * @return 绛惧悕缁撴灉
+ */
+ public static String md5(String data) {
+ return DigestUtils.md5Hex(data).toUpperCase();
+ }
+
+ /**
+ * HMAC-SHA256绛惧悕
+ *
+ * @param data 寰呯鍚嶆暟鎹�
+ * @param secret 瀵嗛挜
+ * @return 绛惧悕缁撴灉锛圔ase64缂栫爜锛�
+ */
+ public static String hmacSha256(String data, String secret) {
+ try {
+ Mac mac = Mac.getInstance("HmacSHA256");
+ SecretKeySpec secretKeySpec = new SecretKeySpec(
+ secret.getBytes(StandardCharsets.UTF_8),
+ "HmacSHA256"
+ );
+ mac.init(secretKeySpec);
+ byte[] bytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
+ return Base64.getEncoder().encodeToString(bytes);
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new RuntimeException("HMAC-SHA256绛惧悕澶辫触", e);
+ }
+ }
+
+ /**
+ * 楠岃瘉HMAC-SHA256绛惧悕
+ *
+ * @param data 鍘熷鏁版嵁
+ * @param secret 瀵嗛挜
+ * @param signature 绛惧悕
+ * @return 鏄惁楠岃瘉閫氳繃
+ */
+ public static boolean verifyHmacSha256(String data, String secret, String signature) {
+ String calculated = hmacSha256(data, secret);
+ return calculated.equals(signature);
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/util/SnowflakeIdGenerator.java b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/util/SnowflakeIdGenerator.java
new file mode 100644
index 0000000..0c0674c
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/infrastructure/util/SnowflakeIdGenerator.java
@@ -0,0 +1,96 @@
+package com.ruoyi.payment.infrastructure.util;
+
+/**
+ * 闆姳绠楁硶ID鐢熸垚鍣�
+ *
+ * @author ruoyi
+ */
+public class SnowflakeIdGenerator {
+
+ // 璧峰鏃堕棿鎴� (2020-01-01)
+ private static final long START_TIMESTAMP = 1577808000000L;
+
+ // 姣忛儴鍒嗗崰鐢ㄧ殑浣嶆暟
+ private static final long SEQUENCE_BIT = 12; // 搴忓垪鍙峰崰鐢ㄧ殑浣嶆暟
+ private static final long MACHINE_BIT = 5; // 鏈哄櫒鏍囪瘑鍗犵敤鐨勪綅鏁�
+ private static final long DATACENTER_BIT = 5; // 鏁版嵁涓績鍗犵敤鐨勪綅鏁�
+
+ // 姣忛儴鍒嗙殑鏈�澶у��
+ private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
+ private static final long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
+ private static final long MAX_DATACENTER_NUM = ~(-1L << DATACENTER_BIT);
+
+ // 姣忛儴鍒嗗悜宸︾殑浣嶇Щ
+ private static final long MACHINE_LEFT = SEQUENCE_BIT;
+ private static final long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
+ private static final long TIMESTAMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
+
+ private static long datacenterId = 1; // 鏁版嵁涓績ID
+ private static long machineId = 1; // 鏈哄櫒ID
+ private static long sequence = 0L; // 搴忓垪鍙�
+ private static long lastTimestamp = -1L; // 涓婁竴娆℃椂闂存埑
+
+ private SnowflakeIdGenerator() {}
+
+ /**
+ * 鐢熸垚鍞竴ID
+ */
+ public static synchronized long generateId() {
+ long currTimestamp = getCurrentTimestamp();
+
+ if (currTimestamp < lastTimestamp) {
+ throw new RuntimeException("鏃堕挓鍥炴嫧锛屾嫆缁濈敓鎴怚D");
+ }
+
+ if (currTimestamp == lastTimestamp) {
+ // 鐩稿悓姣鍐咃紝搴忓垪鍙疯嚜澧�
+ sequence = (sequence + 1) & MAX_SEQUENCE;
+ // 鍚屼竴姣鐨勫簭鍒楁暟宸茬粡杈惧埌鏈�澶�
+ if (sequence == 0L) {
+ currTimestamp = getNextTimestamp();
+ }
+ } else {
+ // 涓嶅悓姣鍐咃紝搴忓垪鍙风疆涓�0
+ sequence = 0L;
+ }
+
+ lastTimestamp = currTimestamp;
+
+ return (currTimestamp - START_TIMESTAMP) << TIMESTAMP_LEFT // 鏃堕棿鎴抽儴鍒�
+ | datacenterId << DATACENTER_LEFT // 鏁版嵁涓績閮ㄥ垎
+ | machineId << MACHINE_LEFT // 鏈哄櫒鏍囪瘑閮ㄥ垎
+ | sequence; // 搴忓垪鍙烽儴鍒�
+ }
+
+ /**
+ * 鑾峰彇褰撳墠鏃堕棿鎴�
+ */
+ private static long getCurrentTimestamp() {
+ return System.currentTimeMillis();
+ }
+
+ /**
+ * 鑾峰彇涓嬩竴涓椂闂存埑
+ */
+ private static long getNextTimestamp() {
+ long timestamp = getCurrentTimestamp();
+ while (timestamp <= lastTimestamp) {
+ timestamp = getCurrentTimestamp();
+ }
+ return timestamp;
+ }
+
+ /**
+ * 璁剧疆鏁版嵁涓績ID鍜屾満鍣↖D
+ */
+ public static void setWorker(long datacenterId, long machineId) {
+ if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
+ throw new IllegalArgumentException("datacenterId 涓嶈兘澶т簬 " + MAX_DATACENTER_NUM + " 鎴栧皬浜� 0");
+ }
+ if (machineId > MAX_MACHINE_NUM || machineId < 0) {
+ throw new IllegalArgumentException("machineId 涓嶈兘澶т簬 " + MAX_MACHINE_NUM + " 鎴栧皬浜� 0");
+ }
+ SnowflakeIdGenerator.datacenterId = datacenterId;
+ SnowflakeIdGenerator.machineId = machineId;
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/callback/PaymentCallback.java b/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/callback/PaymentCallback.java
new file mode 100644
index 0000000..523c71f
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/callback/PaymentCallback.java
@@ -0,0 +1,32 @@
+package com.ruoyi.payment.interfaces.callback;
+
+import com.ruoyi.payment.domain.model.PaymentOrder;
+import com.ruoyi.payment.domain.model.PaymentTransaction;
+
+/**
+ * 鏀粯鍥炶皟鎺ュ彛
+ *
+ * 浣跨敤鏂瑰紡锛�
+ * 鍦ㄤ富椤圭洰涓疄鐜版鎺ュ彛骞舵敞鍐屼负Spring Bean锛屾敮浠樻垚鍔熷悗浼氳嚜鍔ㄨ皟鐢細
+ *
+ * @Component
+ * public class MyPaymentCallback implements PaymentCallback {
+ * @Override
+ * public void onPaymentSuccess(PaymentOrder order, PaymentTransaction transaction) {
+ * // 澶勭悊鏀粯鎴愬姛涓氬姟閫昏緫
+ * log.info("璁㈠崟鏀粯鎴愬姛: {}", order.getBizOrderId());
+ * }
+ * }
+ *
+ * @author ruoyi
+ */
+public interface PaymentCallback {
+
+ /**
+ * 鏀粯鎴愬姛鍥炶皟
+ *
+ * @param order 鏀粯璁㈠崟
+ * @param transaction 鏀粯浜ゆ槗
+ */
+ void onPaymentSuccess(PaymentOrder order, PaymentTransaction transaction);
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/controller/HealthController.java b/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/controller/HealthController.java
new file mode 100644
index 0000000..6d42535
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/controller/HealthController.java
@@ -0,0 +1,38 @@
+package com.ruoyi.payment.interfaces.controller;
+
+import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.payment.common.AjaxResult;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 鍋ュ悍妫�鏌ユ帶鍒跺櫒
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/pay/health")
+public class HealthController {
+
+ /**
+ * 鍋ュ悍妫�鏌�
+ */
+ @Anonymous
+ @GetMapping("/health")
+ public AjaxResult health() {
+ Map<String, Object> data = new HashMap<>();
+ data.put("status", "UP");
+ data.put("service", "dryad-payment");
+ data.put("time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+ data.put("message", "鏀粯妯″潡鏈嶅姟杩愯姝e父");
+ return AjaxResult.success(data);
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/controller/PaymentController.java b/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/controller/PaymentController.java
new file mode 100644
index 0000000..311f0bd
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/controller/PaymentController.java
@@ -0,0 +1,122 @@
+package com.ruoyi.payment.interfaces.controller;
+
+import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.payment.application.service.PaymentService;
+import com.ruoyi.payment.common.AjaxResult;
+import com.ruoyi.payment.domain.model.PaymentOrder;
+import com.ruoyi.payment.domain.model.PaymentTransaction;
+import com.ruoyi.payment.interfaces.dto.PaymentRequest;
+import com.ruoyi.payment.interfaces.dto.PaymentResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 鏀粯鎺ュ彛鎺у埗鍣�
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/pay/payment")
+public class PaymentController {
+
+ @Autowired
+ private PaymentService paymentService;
+
+ @Anonymous()
+ @GetMapping("/hello")
+ public String hello() {
+ return "Hello, World!";
+ }
+ /**
+ * 鍙戣捣寰俊Native鏀粯
+ */
+ @PostMapping("/wechat/native")
+ public AjaxResult createWechatNativePayment(@Validated @RequestBody PaymentRequest request) {
+ try {
+ PaymentResponse response = paymentService.createWechatNativePayment(request);
+ return AjaxResult.success(response);
+ } catch (Exception e) {
+ log.error("鍙戣捣寰俊Native鏀粯澶辫触", e);
+ return AjaxResult.error("鏀粯鍒涘缓澶辫触: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 鍙戣捣鏀粯瀹濆綋闈粯
+ */
+ @PostMapping("/alipay/precreate")
+ public AjaxResult createAlipayPrecreate(@Validated @RequestBody PaymentRequest request) {
+ try {
+ PaymentResponse response = paymentService.createAlipayPrecreate(request);
+ return AjaxResult.success(response);
+ } catch (Exception e) {
+ log.error("鍙戣捣鏀粯瀹濆綋闈粯澶辫触", e);
+ return AjaxResult.error("鏀粯鍒涘缓澶辫触: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 鍙戣捣鏀粯瀹濆綋闈粯锛堢涓夋柟鎺ュ彛锛�
+ */
+ @PostMapping("/alipay/thirdparty/precreate")
+ public AjaxResult createAlipayThirdPartyPrecreate(@Validated @RequestBody PaymentRequest request) {
+ try {
+ PaymentResponse response = paymentService.createAlipayThirdPartyPrecreate(request);
+ return AjaxResult.success(response);
+ } catch (Exception e) {
+ log.error("鍙戣捣鏀粯瀹濆綋闈粯锛堢涓夋柟鎺ュ彛锛夊け璐�", e);
+ return AjaxResult.error("鏀粯鍒涘缓澶辫触: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 鏌ヨ璁㈠崟
+ */
+ @GetMapping("/orders/{orderId}")
+ public AjaxResult getOrder(@PathVariable Long orderId) {
+ try {
+ PaymentOrder order = paymentService.getOrder(orderId);
+ if (order == null) {
+ return AjaxResult.error("璁㈠崟涓嶅瓨鍦�");
+ }
+ return AjaxResult.success(order);
+ } catch (Exception e) {
+ log.error("鏌ヨ璁㈠崟澶辫触", e);
+ return AjaxResult.error("鏌ヨ澶辫触: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 鏌ヨ鏈�鏂颁氦鏄�
+ */
+ @GetMapping("/orders/{orderId}/transactions/latest")
+ public AjaxResult getLatestTransaction(@PathVariable Long orderId) {
+ try {
+ PaymentTransaction transaction = paymentService.getLatestTransaction(orderId);
+ if (transaction == null) {
+ return AjaxResult.error("浜ゆ槗涓嶅瓨鍦�");
+ }
+ return AjaxResult.success(transaction);
+ } catch (Exception e) {
+ log.error("鏌ヨ浜ゆ槗澶辫触", e);
+ return AjaxResult.error("鏌ヨ澶辫触: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 鏌ヨ鏀粯瀹濈涓夋柟浜ゆ槗鐘舵��
+ */
+ @GetMapping("/alipay/thirdparty/query/{orderId}")
+ public AjaxResult queryAlipayThirdPartyTradeStatus(@PathVariable Long orderId) {
+ try {
+ String tradeStatus = paymentService.queryAlipayThirdPartyTradeStatus(orderId);
+ return AjaxResult.success(tradeStatus);
+ } catch (Exception e) {
+ log.error("鏌ヨ鏀粯瀹濈涓夋柟浜ゆ槗鐘舵�佸け璐�", e);
+ return AjaxResult.error("鏌ヨ澶辫触: " + e.getMessage());
+ }
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/controller/PaymentNotifyController.java b/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/controller/PaymentNotifyController.java
new file mode 100644
index 0000000..c8721a8
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/controller/PaymentNotifyController.java
@@ -0,0 +1,128 @@
+package com.ruoyi.payment.interfaces.controller;
+
+import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.payment.application.service.PaymentNotifyService;
+import com.ruoyi.payment.infrastructure.channel.alipay.AlipayUtil;
+import com.ruoyi.payment.infrastructure.channel.wechat.WxPayUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.BufferedReader;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 娓犻亾鍥炶皟鎺ュ彛鎺у埗鍣�
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/pay/notify")
+public class PaymentNotifyController {
+
+ @Autowired
+ private WxPayUtil wxPayUtil;
+
+ @Autowired
+ private AlipayUtil alipayUtil;
+
+ @Autowired
+ private PaymentNotifyService paymentNotifyService;
+
+ /**
+ * 寰俊鏀粯鍥炶皟
+ */
+ @Anonymous
+ @PostMapping("/wechat")
+ public String wechatNotify(HttpServletRequest request) {
+ try {
+ log.info("鎺ユ敹鍒板井淇℃敮浠樺洖璋�");
+
+ // 1. 璇诲彇XML鎶ユ枃
+ String xmlData = readRequestBody(request);
+ log.info("寰俊鍥炶皟鎶ユ枃: {}", xmlData);
+
+ // 2. 瑙f瀽XML涓篗ap
+ Map<String, String> params = wxPayUtil.xmlToMap(xmlData);
+
+ // 3. 楠岃瘉绛惧悕
+ if (!wxPayUtil.verifySign(params)) {
+ log.error("寰俊鍥炶皟绛惧悕楠岃瘉澶辫触");
+ return wxPayUtil.buildFailResponse("绛惧悕楠岃瘉澶辫触");
+ }
+
+ log.info("寰俊鍥炶皟绛惧悕楠岃瘉閫氳繃");
+
+ // 4. 澶勭悊鍥炶皟锛堟洿鏂拌鍗曠姸鎬併�佽Е鍙戜笟鍔″洖璋冿級
+ paymentNotifyService.processWechatNotify(params, xmlData);
+
+ // 5. 杩斿洖寰俊瑕佹眰鐨刋ML搴旂瓟
+ return wxPayUtil.buildSuccessResponse();
+ } catch (Exception e) {
+ log.error("澶勭悊寰俊鍥炶皟澶辫触", e);
+ return wxPayUtil.buildFailResponse("澶勭悊澶辫触");
+ }
+ }
+
+ @Anonymous
+ @GetMapping("/hello")
+ public String hello() {
+ return "hello";
+ }
+
+ /**
+ * 鏀粯瀹濆洖璋�
+ */
+ @Anonymous
+ @PostMapping("/alipay")
+ public String alipayNotify(HttpServletRequest request) {
+ try {
+ log.info("鎺ユ敹鍒版敮浠樺疂鍥炶皟");
+
+ // 1. 鑾峰彇鎵�鏈夊弬鏁�
+ Map<String, String> params = new HashMap<>();
+ Enumeration<String> parameterNames = request.getParameterNames();
+ while (parameterNames.hasMoreElements()) {
+ String name = parameterNames.nextElement();
+ params.put(name, request.getParameter(name));
+ }
+
+ log.info("鏀粯瀹濆洖璋冨弬鏁�: {}", params);
+
+ // 2. 楠岃瘉绛惧悕
+ if (!alipayUtil.verifySign(params)) {
+ log.error("鏀粯瀹濆洖璋冪鍚嶉獙璇佸け璐�");
+ return "fail";
+ }
+
+ log.info("鏀粯瀹濆洖璋冪鍚嶉獙璇侀�氳繃");
+
+ // 3. 澶勭悊鍥炶皟锛堟洿鏂拌鍗曠姸鎬併�佽Е鍙戜笟鍔″洖璋冿級
+ paymentNotifyService.processAlipayNotify(params);
+
+ // 4. 杩斿洖鏀粯瀹濊姹傜殑搴旂瓟
+ return "success";
+ } catch (Exception e) {
+ log.error("澶勭悊鏀粯瀹濆洖璋冨け璐�", e);
+ return "fail";
+ }
+ }
+
+ /**
+ * 璇诲彇璇锋眰浣�
+ */
+ private String readRequestBody(HttpServletRequest request) throws Exception {
+ StringBuilder sb = new StringBuilder();
+ try (BufferedReader reader = request.getReader()) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line);
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/dto/PaymentRequest.java b/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/dto/PaymentRequest.java
new file mode 100644
index 0000000..ec9f5fd
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/dto/PaymentRequest.java
@@ -0,0 +1,38 @@
+package com.ruoyi.payment.interfaces.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+ * 鍙戣捣鏀粯璇锋眰
+ *
+ * @author ruoyi
+ */
+@Data
+public class PaymentRequest implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /** 涓氬姟璁㈠崟鍙� */
+ @NotBlank(message = "涓氬姟璁㈠崟鍙蜂笉鑳戒负绌�")
+ private String bizOrderId;
+
+ /** 閲戦锛堝垎锛� */
+ @NotNull(message = "閲戦涓嶈兘涓虹┖")
+ @Min(value = 1, message = "閲戦蹇呴』澶т簬0")
+ private Integer amount;
+
+ /** 璁㈠崟鏍囬 */
+ @NotBlank(message = "璁㈠崟鏍囬涓嶈兘涓虹┖")
+ private String subject;
+
+ /** 璁㈠崟鎻忚堪 */
+ private String description;
+
+ /** 涓氬姟鍥炶皟鍦板潃 */
+ @NotBlank(message = "鍥炶皟鍦板潃涓嶈兘涓虹┖")
+ private String callbackUrl;
+}
diff --git a/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/dto/PaymentResponse.java b/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/dto/PaymentResponse.java
new file mode 100644
index 0000000..b676b38
--- /dev/null
+++ b/dryad-payment/src/main/java/com/ruoyi/payment/interfaces/dto/PaymentResponse.java
@@ -0,0 +1,33 @@
+package com.ruoyi.payment.interfaces.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 鏀粯鍝嶅簲
+ *
+ * @author ruoyi
+ */
+@Data
+public class PaymentResponse implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /** 璁㈠崟ID */
+ private Long orderId;
+
+ /** 浜ゆ槗ID */
+ private Long transactionId;
+
+ /** 璁㈠崟鐘舵�� */
+ private String status;
+
+ /** Base64浜岀淮鐮� */
+ private String qrBase64;
+
+ /** 杩囨湡鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime expireAt;
+}
diff --git a/dryad-payment/src/main/resources/META-INF/spring.factories b/dryad-payment/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..5b5c043
--- /dev/null
+++ b/dryad-payment/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.ruoyi.payment.config.PaymentAutoConfiguration
diff --git a/dryad-payment/src/main/resources/application.yml b/dryad-payment/src/main/resources/application.yml
new file mode 100644
index 0000000..a8f3351
--- /dev/null
+++ b/dryad-payment/src/main/resources/application.yml
@@ -0,0 +1,98 @@
+server:
+ port: 8080
+ servlet:
+ context-path: /
+
+spring:
+ application:
+ name: dryad-payment
+
+ datasource:
+ type: com.alibaba.druid.pool.DruidDataSource
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/dryad_payment?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+ username: root
+ password: abcd1234
+ druid:
+ initial-size: 5
+ min-idle: 5
+ max-active: 20
+ max-wait: 60000
+ time-between-eviction-runs-millis: 60000
+ min-evictable-idle-time-millis: 300000
+ validation-query: SELECT 1 FROM DUAL
+ test-while-idle: true
+ test-on-borrow: false
+ test-on-return: false
+
+# MyBatis Plus閰嶇疆
+mybatis-plus:
+ mapper-locations: classpath*:mapper/**/*Mapper.xml
+ type-aliases-package: com.ruoyi.payment.domain.model
+ configuration:
+ map-underscore-to-camel-case: true
+ cache-enabled: false
+ log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+ global-config:
+ db-config:
+ id-type: ASSIGN_ID
+ logic-delete-field: deleted
+ logic-delete-value: 1
+ logic-not-delete-value: 0
+
+# 鏃ュ織閰嶇疆
+logging:
+ level:
+ root: INFO
+ com.ruoyi.payment: DEBUG
+ pattern:
+ console: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n'
+
+# 鏀粯閰嶇疆
+payment:
+ # 鏄惁鍚敤鏀粯妯″潡锛堜綔涓虹粍浠堕泦鎴愭椂鍙�氳繃姝ゅ紑鍏虫帶鍒讹級
+ enabled: true
+
+ # 寰俊鏀粯閰嶇疆
+ wechat:
+ appId: wx70f6a7346ee842c0
+ mchId: 1573728151
+ appName: 姘戣埅鍖荤枟蹇嚎
+ mchKey: Xz0ClPK3f5sCeT6SGTaha1vpVmyUFcbp
+ notifyUrl: https://api.966120.com/api/pay/notify/wechat
+ signType: MD5
+ checkSign: false
+
+ # 鏀粯瀹濋厤缃�
+ alipay:
+ appId: 2018060160299486
+ privateKey: MIIEpgIBAAKCAQEArt273bWTEPXPjCsUYwFx7CNjhcQlm1NtbNjfeIsZ2g9sbFCQP9qpyufp6zkBv6eq+6WEztkC1KwSsuDjP5LvgY/1pmGFlr8r7cjeZI4bTeIe9jG5UaHolnzbdXUlSoInzgWRvbYXxuQZciwVpokwviW27YK9wPIzz9OTiRquL8b3YWPZLO7xK0gBMa2KfFfUXxCB8gHJtidQ+FjjYXb2WpnScKLJdKfWcGWFnyGiZOknyFR9kI8cm0cYPNHtecQId0bQ1ee7YDLD8dBPd2Pd/JBC4Wn6HuOvZOLqZvIpIj+8q0zGXjUUns6MsjNL3MUKuhKy6hQGwP5sGrPcVcwqiwIDAQABAoIBAQCTeW9iSSsRx61VUlOsN+DDPQlHHCh3OcH0ZWb6e52+2Pkg1EUDhT9jX3lZJsfBwf8iofJCnKSVhdVzRNSCnkIdq7KJsn9+phW/QYPFnE+MvKJOEZtwLDNDD2PqSHS9xM0bJHlIXNTqqR6IuoM740HXa2k+H+A2ZE2r/YzUuUqkASwAYPKYzWa1wivg4CZrvoPZ5bXvYOHoV0jZEtyUQB9dHuCz+bghbR+28vGkYwEGInLsOCe6Gl5D61F0l2qAXRQky3P0jXxIPXPFmhBYutAAUufLpryruQgL3MDDm5dhBJpwp89qwFDjc8fWVS/FFYJ0KDQOpAxAI800YHgaJ4VBAoGBAOdCcoYS49Q9Iula7gFGVXeto7QSUsP0CnQZM/tsAU6TiX3XG5pxBYoVmYSIQPylgagRTBZHD5t/LGa+I+KYMHSVAH/kndPdgTO7EiwfUCzi1DmGZVWs7XJ2zRfTmYRVdsElPy3Z8Pm802jd7mhffw/5p6y9pzNJqOjmGOUbYGrrAoGBAMGS1CGHkK4nD2BsIWJoKW2Lxph3Bq5hN1iaE8ZjOAvFT9drNqfwRa6BVGyfYEXPZTvUT0FUNMdukcTCM6O3FRU8EABJIcVue2QA+BvkfwPEU3JxMA8DrWHO7IcgtG9wjbxretDsf+SZmkQgK0ZUPod5ZZSycFxM3/GiXDQjGhbhAoGBAL/Y/+j6AscvcKbmKEwmbQC7q/LWwJKPAZ0Oy3DoSK1G9+jNarjUyiOjh5fK8R6mrskekGBq0yfMeKlDU8HHP2t3sNJodgYs2+JubsTrtTeHdUfDlo1cyB8NL1d00wZVYA8bNy5yftavLzLv6bfsgRxfoBpNu0dw9A9B06U88N/BAoGBAKCN/nEJFlG8iB570Xzj1GjOJJzVLK96ZwOQWJKWPShWMhEFFkJZIhLJppKp5ppAmUD0qgAPre80oKdIRLin5E7GkKcMAXzWVHXv79qCvW8MagJkK25oqGiVzs2NrNs5yfXcV/PuFW4wkSmsXPhqa6rGYCDjmBqWkLDE8CE2dC9BAoGBAJ2+QfAAvB27mr48+vY4HxZyPRrCBA1YkokraWQ0IlfD0MKsFw0Af4Iu3oy7V6NlE2GwL/AcObyHeGZt7DLbViAQOgmb9BpUrjUZ4bXSBuGPfRe11HCu6j6W67qa76TAoy3A0Dfm0OE/m9r4H99NaLzeBm1KluySKkfYXoqyueQw
+ alipayPublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl8BjXYaudmGT+sEos0AXEUrKl6+wyw6++hBoJDdY7P+P7poa34GN4YSkkavTA/HplmRM1wTLcY+NqhxhLpNrcgy08AbC/GGcLM2wxQGFa+L+DQLz34uBAShXDK8yN6O49UdbbJ2RRaJSAb+nW4ZVCPMGtMu4S3lXTymQgizM1IYo9L92U5QPRzZRSP8+AmQPzwofRqEgvkO02s66xU2G5AAdkVg5BQm34eM0Io2CmcWF9jSoWQTJdyd7tw3oec9NqD7x3CfcsN3NAJOQLz4+bWWqDWelyviyAr2reeH6AuBVjaWwAvAJx3yuLevKMXTzPC95Ja7w4XYSB9Fg2+aKmwIDAQAB
+ serverUrl: https://openapi.alipay.com/gateway.do
+ notifyUrl: https://api.966120.com/api/pay/notify/alipay
+ signType: RSA2
+ checkSign: true
+ # 鏀粯鏂瑰紡: OFFICIAL(瀹樻柟鏀粯瀹�) 鎴� THIRD_PARTY(绗笁鏂规敮浠樺疂)
+ paymentMethod: OFFICIAL
+ # 绗笁鏂规敮浠橀厤缃�
+ thirdParty:
+ enabled: true
+ url: https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php
+ defaultNotifyUrl: https://dsp.966120.com.cn/alipay/pay_notify
+ timeout: 30000 # 瓒呮椂鏃堕棿锛堟绉掞級
+
+ # 涓氬姟鍥炶皟閰嶇疆
+ business:
+ callbackSignSecret: your-hmac-secret-key-here
+ callbackRetryMaxCount: 10
+ callbackRetryIntervals: 0,1,5,15,60
+
+ # 浜岀淮鐮侀厤缃�
+ qrcode:
+ size: 300
+ format: PNG
+
+ # 瀵硅处閰嶇疆
+ reconciliation:
+ enabled: true
+ cron: "0 0 2 * * ?"
\ No newline at end of file
diff --git a/dryad-payment/src/main/resources/mapper/BizCallbackLogMapper.xml b/dryad-payment/src/main/resources/mapper/BizCallbackLogMapper.xml
new file mode 100644
index 0000000..7a4e249
--- /dev/null
+++ b/dryad-payment/src/main/resources/mapper/BizCallbackLogMapper.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.payment.infrastructure.persistence.mapper.BizCallbackLogMapper">
+
+ <resultMap id="BizCallbackLogResult" type="com.ruoyi.payment.domain.model.BizCallbackLog">
+ <id property="id" column="id"/>
+ <result property="orderId" column="order_id"/>
+ <result property="transactionId" column="transaction_id"/>
+ <result property="callbackUrl" column="callback_url"/>
+ <result property="payload" column="payload"/>
+ <result property="httpStatus" column="http_status"/>
+ <result property="response" column="response"/>
+ <result property="success" column="success"/>
+ <result property="retryCount" column="retry_count"/>
+ <result property="lastRetryAt" column="last_retry_at"/>
+ <result property="createdAt" column="created_at"/>
+ </resultMap>
+
+ <insert id="insert" parameterType="com.ruoyi.payment.domain.model.BizCallbackLog">
+ INSERT INTO pay_biz_callback_log (
+ id, order_id, transaction_id, callback_url, payload,
+ http_status, response, success, retry_count, last_retry_at, created_at
+ ) VALUES (
+ #{id}, #{orderId}, #{transactionId}, #{callbackUrl}, #{payload},
+ #{httpStatus}, #{response}, #{success}, #{retryCount}, #{lastRetryAt}, #{createdAt}
+ )
+ </insert>
+
+ <update id="update" parameterType="com.ruoyi.payment.domain.model.BizCallbackLog">
+ UPDATE pay_biz_callback_log
+ <set>
+ <if test="orderId != null">order_id = #{orderId},</if>
+ <if test="transactionId != null">transaction_id = #{transactionId},</if>
+ <if test="callbackUrl != null">callback_url = #{callbackUrl},</if>
+ <if test="payload != null">payload = #{payload},</if>
+ <if test="httpStatus != null">http_status = #{httpStatus},</if>
+ <if test="response != null">response = #{response},</if>
+ <if test="success != null">success = #{success},</if>
+ <if test="retryCount != null">retry_count = #{retryCount},</if>
+ <if test="lastRetryAt != null">last_retry_at = #{lastRetryAt},</if>
+ </set>
+ WHERE id = #{id}
+ </update>
+
+ <select id="selectById" resultMap="BizCallbackLogResult">
+ SELECT * FROM pay_biz_callback_log
+ WHERE id = #{id}
+ </select>
+
+</mapper>
diff --git a/dryad-payment/src/main/resources/mapper/NotifyLogMapper.xml b/dryad-payment/src/main/resources/mapper/NotifyLogMapper.xml
new file mode 100644
index 0000000..47db5a7
--- /dev/null
+++ b/dryad-payment/src/main/resources/mapper/NotifyLogMapper.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.payment.infrastructure.persistence.mapper.NotifyLogMapper">
+
+ <resultMap id="NotifyLogResult" type="com.ruoyi.payment.domain.model.NotifyLog">
+ <id property="id" column="id"/>
+ <result property="channel" column="channel"/>
+ <result property="notifyIdOrSerial" column="notify_id_or_serial"/>
+ <result property="orderId" column="order_id"/>
+ <result property="transactionId" column="transaction_id"/>
+ <result property="payload" column="payload"/>
+ <result property="verified" column="verified"/>
+ <result property="processed" column="processed"/>
+ <result property="result" column="result"/>
+ <result property="createdAt" column="created_at"/>
+ </resultMap>
+
+ <insert id="insert" parameterType="com.ruoyi.payment.domain.model.NotifyLog">
+ INSERT INTO pay_notify_log (
+ id, channel, notify_id_or_serial, order_id, transaction_id,
+ payload, verified, processed, result, created_at
+ ) VALUES (
+ #{id}, #{channel}, #{notifyIdOrSerial}, #{orderId}, #{transactionId},
+ #{payload}, #{verified}, #{processed}, #{result}, #{createdAt}
+ )
+ </insert>
+
+ <update id="update" parameterType="com.ruoyi.payment.domain.model.NotifyLog">
+ UPDATE pay_notify_log
+ <set>
+ <if test="channel != null">channel = #{channel},</if>
+ <if test="notifyIdOrSerial != null">notify_id_or_serial = #{notifyIdOrSerial},</if>
+ <if test="orderId != null">order_id = #{orderId},</if>
+ <if test="transactionId != null">transaction_id = #{transactionId},</if>
+ <if test="payload != null">payload = #{payload},</if>
+ <if test="verified != null">verified = #{verified},</if>
+ <if test="processed != null">processed = #{processed},</if>
+ <if test="result != null">result = #{result},</if>
+ </set>
+ WHERE id = #{id}
+ </update>
+
+ <select id="selectById" resultMap="NotifyLogResult">
+ SELECT * FROM pay_notify_log
+ WHERE id = #{id}
+ </select>
+
+ <select id="selectProcessedCount" resultType="int">
+ SELECT COUNT(*) FROM pay_notify_log
+ WHERE channel = #{channel}
+ AND notify_id_or_serial = #{notifyIdOrSerial}
+ AND processed = 1
+ </select>
+
+</mapper>
diff --git a/dryad-payment/src/main/resources/mapper/OperationAuditMapper.xml b/dryad-payment/src/main/resources/mapper/OperationAuditMapper.xml
new file mode 100644
index 0000000..70ad48f
--- /dev/null
+++ b/dryad-payment/src/main/resources/mapper/OperationAuditMapper.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.payment.infrastructure.persistence.mapper.OperationAuditMapper">
+
+ <resultMap id="OperationAuditResult" type="com.ruoyi.payment.domain.model.OperationAudit">
+ <id property="id" column="id"/>
+ <result property="operator" column="operator"/>
+ <result property="operationType" column="operation_type"/>
+ <result property="orderId" column="order_id"/>
+ <result property="transactionId" column="transaction_id"/>
+ <result property="params" column="params"/>
+ <result property="approved" column="approved"/>
+ <result property="createdAt" column="created_at"/>
+ </resultMap>
+
+ <insert id="insert" parameterType="com.ruoyi.payment.domain.model.OperationAudit">
+ INSERT INTO pay_operation_audit (
+ id, operator, operation_type, order_id, transaction_id,
+ params, approved, created_at
+ ) VALUES (
+ #{id}, #{operator}, #{operationType}, #{orderId}, #{transactionId},
+ #{params}, #{approved}, #{createdAt}
+ )
+ </insert>
+
+ <select id="selectById" resultMap="OperationAuditResult">
+ SELECT * FROM pay_operation_audit
+ WHERE id = #{id}
+ </select>
+
+</mapper>
diff --git a/dryad-payment/src/main/resources/mapper/PaymentOrderMapper.xml b/dryad-payment/src/main/resources/mapper/PaymentOrderMapper.xml
new file mode 100644
index 0000000..063c689
--- /dev/null
+++ b/dryad-payment/src/main/resources/mapper/PaymentOrderMapper.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.payment.infrastructure.persistence.mapper.PaymentOrderMapper">
+
+ <resultMap id="PaymentOrderResult" type="com.ruoyi.payment.domain.model.PaymentOrder">
+ <id property="id" column="id"/>
+ <result property="bizOrderId" column="biz_order_id"/>
+ <result property="amount" column="amount"/>
+ <result property="currency" column="currency"/>
+ <result property="channel" column="channel"/>
+ <result property="status" column="status"/>
+ <result property="subject" column="subject"/>
+ <result property="description" column="description"/>
+ <result property="callbackUrl" column="callback_url"/>
+ <result property="expireAt" column="expire_at"/>
+ <result property="latestTransactionId" column="latest_transaction_id"/>
+ <result property="channelTradeNo" column="channel_trade_no"/>
+ <result property="paidAt" column="paid_at"/>
+ <result property="version" column="version"/>
+ <result property="createdAt" column="created_at"/>
+ <result property="updatedAt" column="updated_at"/>
+ </resultMap>
+
+ <insert id="insert" parameterType="com.ruoyi.payment.domain.model.PaymentOrder">
+ INSERT INTO pay_order (
+ id, biz_order_id, amount, currency, channel, status,
+ subject, description, callback_url, expire_at,
+ latest_transaction_id, channel_trade_no, paid_at,
+ version, created_at, updated_at
+ ) VALUES (
+ #{id}, #{bizOrderId}, #{amount}, #{currency}, #{channel}, #{status},
+ #{subject}, #{description}, #{callbackUrl}, #{expireAt},
+ #{latestTransactionId}, #{channelTradeNo}, #{paidAt},
+ #{version}, #{createdAt}, #{updatedAt}
+ )
+ </insert>
+
+ <update id="update" parameterType="com.ruoyi.payment.domain.model.PaymentOrder">
+ UPDATE pay_order
+ <set>
+ <if test="bizOrderId != null">biz_order_id = #{bizOrderId},</if>
+ <if test="amount != null">amount = #{amount},</if>
+ <if test="currency != null">currency = #{currency},</if>
+ <if test="channel != null">channel = #{channel},</if>
+ <if test="status != null">status = #{status},</if>
+ <if test="subject != null">subject = #{subject},</if>
+ <if test="description != null">description = #{description},</if>
+ <if test="callbackUrl != null">callback_url = #{callbackUrl},</if>
+ <if test="expireAt != null">expire_at = #{expireAt},</if>
+ <if test="latestTransactionId != null">latest_transaction_id = #{latestTransactionId},</if>
+ <if test="channelTradeNo != null">channel_trade_no = #{channelTradeNo},</if>
+ <if test="paidAt != null">paid_at = #{paidAt},</if>
+ <if test="version != null">version = #{version},</if>
+ <if test="updatedAt != null">updated_at = #{updatedAt},</if>
+ </set>
+ WHERE id = #{id}
+ </update>
+
+ <select id="selectById" resultMap="PaymentOrderResult">
+ SELECT * FROM pay_order
+ WHERE id = #{id}
+ </select>
+
+ <select id="selectByBizOrderIdAndChannel" resultMap="PaymentOrderResult">
+ SELECT * FROM pay_order
+ WHERE biz_order_id = #{bizOrderId}
+ AND channel = #{channel}
+ LIMIT 1
+ </select>
+
+ <select id="selectByChannelTradeNo" resultMap="PaymentOrderResult">
+ SELECT * FROM pay_order
+ WHERE channel_trade_no = #{channelTradeNo}
+ LIMIT 1
+ </select>
+
+</mapper>
diff --git a/dryad-payment/src/main/resources/mapper/PaymentTransactionMapper.xml b/dryad-payment/src/main/resources/mapper/PaymentTransactionMapper.xml
new file mode 100644
index 0000000..6f721f7
--- /dev/null
+++ b/dryad-payment/src/main/resources/mapper/PaymentTransactionMapper.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.payment.infrastructure.persistence.mapper.PaymentTransactionMapper">
+
+ <resultMap id="PaymentTransactionResult" type="com.ruoyi.payment.domain.model.PaymentTransaction">
+ <id property="id" column="id"/>
+ <result property="orderId" column="order_id"/>
+ <result property="channel" column="channel"/>
+ <result property="clientType" column="client_type"/>
+ <result property="status" column="status"/>
+ <result property="codeOrQr" column="code_or_qr"/>
+ <result property="qrBase64" column="qr_base64"/>
+ <result property="requestParams" column="request_params"/>
+ <result property="responseSnapshot" column="response_snapshot"/>
+ <result property="channelTradeNo" column="channel_trade_no"/>
+ <result property="createdAt" column="created_at"/>
+ <result property="paidAt" column="paid_at"/>
+ </resultMap>
+
+ <insert id="insert" parameterType="com.ruoyi.payment.domain.model.PaymentTransaction">
+ INSERT INTO pay_transaction (
+ id, order_id, channel, client_type, status,
+ code_or_qr, qr_base64, request_params, response_snapshot,
+ channel_trade_no, created_at, paid_at
+ ) VALUES (
+ #{id}, #{orderId}, #{channel}, #{clientType}, #{status},
+ #{codeOrQr}, #{qrBase64}, #{requestParams}, #{responseSnapshot},
+ #{channelTradeNo}, #{createdAt}, #{paidAt}
+ )
+ </insert>
+
+ <update id="update" parameterType="com.ruoyi.payment.domain.model.PaymentTransaction">
+ UPDATE pay_transaction
+ <set>
+ <if test="orderId != null">order_id = #{orderId},</if>
+ <if test="channel != null">channel = #{channel},</if>
+ <if test="clientType != null">client_type = #{clientType},</if>
+ <if test="status != null">status = #{status},</if>
+ <if test="codeOrQr != null">code_or_qr = #{codeOrQr},</if>
+ <if test="qrBase64 != null">qr_base64 = #{qrBase64},</if>
+ <if test="requestParams != null">request_params = #{requestParams},</if>
+ <if test="responseSnapshot != null">response_snapshot = #{responseSnapshot},</if>
+ <if test="channelTradeNo != null">channel_trade_no = #{channelTradeNo},</if>
+ <if test="paidAt != null">paid_at = #{paidAt},</if>
+ </set>
+ WHERE id = #{id}
+ </update>
+
+ <select id="selectById" resultMap="PaymentTransactionResult">
+ SELECT * FROM pay_transaction
+ WHERE id = #{id}
+ </select>
+
+ <select id="selectPendingByOrderId" resultMap="PaymentTransactionResult">
+ SELECT * FROM pay_transaction
+ WHERE order_id = #{orderId}
+ AND status = 'PENDING'
+ LIMIT 1
+ </select>
+
+ <select id="selectByChannelTradeNo" resultMap="PaymentTransactionResult">
+ SELECT * FROM pay_transaction
+ WHERE channel_trade_no = #{channelTradeNo}
+ LIMIT 1
+ </select>
+
+ <select id="selectLatestByOrderId" resultMap="PaymentTransactionResult">
+ SELECT * FROM pay_transaction
+ WHERE order_id = #{orderId}
+ ORDER BY created_at DESC
+ LIMIT 1
+ </select>
+
+</mapper>
diff --git a/dryad-payment/src/main/resources/sql/schema.sql b/dryad-payment/src/main/resources/sql/schema.sql
new file mode 100644
index 0000000..4f2047f
--- /dev/null
+++ b/dryad-payment/src/main/resources/sql/schema.sql
@@ -0,0 +1,134 @@
+-- 鍒涘缓鏁版嵁搴�
+CREATE DATABASE IF NOT EXISTS `dryad_payment` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
+USE `dryad_payment`;
+
+-- 鍒犻櫎宸插瓨鍦ㄧ殑琛�
+DROP TABLE IF EXISTS `pay_reconciliation_result`;
+DROP TABLE IF EXISTS `pay_reconciliation_task`;
+DROP TABLE IF EXISTS `pay_operation_audit`;
+DROP TABLE IF EXISTS `pay_biz_callback_log`;
+DROP TABLE IF EXISTS `pay_notify_log`;
+DROP TABLE IF EXISTS `pay_transaction`;
+DROP TABLE IF EXISTS `pay_order`;
+
+-- 鏀粯璁㈠崟琛�
+CREATE TABLE `pay_order` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '璁㈠崟ID',
+ `biz_order_id` VARCHAR(64) NOT NULL COMMENT '涓氬姟璁㈠崟鍙�',
+ `amount` INT NOT NULL COMMENT '閲戦锛堝垎锛�',
+ `currency` VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '甯佺',
+ `channel` VARCHAR(16) NOT NULL COMMENT '鏀粯娓犻亾',
+ `status` VARCHAR(16) NOT NULL COMMENT '璁㈠崟鐘舵��',
+ `subject` VARCHAR(128) NOT NULL COMMENT '璁㈠崟鏍囬',
+ `description` VARCHAR(512) COMMENT '璁㈠崟鎻忚堪',
+ `callback_url` VARCHAR(512) NOT NULL COMMENT '涓氬姟鍥炶皟鍦板潃',
+ `expire_at` DATETIME NOT NULL COMMENT '杩囨湡鏃堕棿',
+ `latest_transaction_id` BIGINT COMMENT '鏈�鏂颁氦鏄揑D',
+ `channel_trade_no` VARCHAR(64) COMMENT '娓犻亾浜ゆ槗鍙�',
+ `paid_at` DATETIME COMMENT '鏀粯鎴愬姛鏃堕棿',
+ `version` INT NOT NULL DEFAULT 0 COMMENT '涔愯閿佺増鏈彿',
+ `created_at` DATETIME NOT NULL COMMENT '鍒涘缓鏃堕棿',
+ `updated_at` DATETIME NOT NULL COMMENT '鏇存柊鏃堕棿',
+ INDEX `idx_biz_order_id` (`biz_order_id`),
+ INDEX `idx_channel_trade_no` (`channel_trade_no`),
+ INDEX `idx_status` (`status`),
+ INDEX `idx_expire_at` (`expire_at`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鏀粯璁㈠崟琛�';
+
+-- 鏀粯浜ゆ槗娴佹按琛�
+CREATE TABLE `pay_transaction` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '浜ゆ槗ID',
+ `order_id` BIGINT NOT NULL COMMENT '璁㈠崟ID',
+ `channel` VARCHAR(16) NOT NULL COMMENT '鏀粯娓犻亾',
+ `client_type` VARCHAR(32) NOT NULL COMMENT '瀹㈡埛绔被鍨�',
+ `status` VARCHAR(16) NOT NULL COMMENT '浜ゆ槗鐘舵��',
+ `code_or_qr` VARCHAR(512) COMMENT '浜岀淮鐮佸唴瀹�',
+ `qr_base64` TEXT COMMENT 'Base64浜岀淮鐮佸浘鐗�',
+ `request_params` TEXT COMMENT '璇锋眰鍙傛暟蹇収',
+ `response_snapshot` TEXT COMMENT '鍝嶅簲蹇収',
+ `channel_trade_no` VARCHAR(64) COMMENT '娓犻亾浜ゆ槗鍙�',
+ `created_at` DATETIME NOT NULL COMMENT '鍒涘缓鏃堕棿',
+ `paid_at` DATETIME COMMENT '鏀粯瀹屾垚鏃堕棿',
+ INDEX `idx_order_id` (`order_id`),
+ INDEX `idx_channel_trade_no` (`channel_trade_no`),
+ INDEX `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鏀粯浜ゆ槗娴佹按琛�';
+
+-- 娓犻亾鍥炶皟鏃ュ織琛�
+CREATE TABLE `pay_notify_log` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '鏃ュ織ID',
+ `channel` VARCHAR(16) NOT NULL COMMENT '鏀粯娓犻亾',
+ `notify_id_or_serial` VARCHAR(64) NOT NULL COMMENT '娓犻亾閫氱煡鍞竴鏍囪瘑',
+ `order_id` BIGINT COMMENT '璁㈠崟ID',
+ `transaction_id` BIGINT COMMENT '浜ゆ槗ID',
+ `payload` TEXT NOT NULL COMMENT '鍥炶皟鍘熷鎶ユ枃',
+ `verified` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '绛惧悕楠岃瘉鏄惁閫氳繃',
+ `processed` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '鏄惁宸插鐞�',
+ `result` VARCHAR(128) COMMENT '澶勭悊缁撴灉',
+ `created_at` DATETIME NOT NULL COMMENT '鎺ユ敹鏃堕棿',
+ UNIQUE KEY `uk_channel_notify` (`channel`, `notify_id_or_serial`),
+ INDEX `idx_order_id` (`order_id`),
+ INDEX `idx_transaction_id` (`transaction_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='娓犻亾鍥炶皟鏃ュ織琛�';
+
+-- 涓氬姟鍥炶皟鏃ュ織琛�
+CREATE TABLE `pay_biz_callback_log` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '鏃ュ織ID',
+ `order_id` BIGINT NOT NULL COMMENT '璁㈠崟ID',
+ `transaction_id` BIGINT NOT NULL COMMENT '浜ゆ槗ID',
+ `callback_url` VARCHAR(512) NOT NULL COMMENT '鍥炶皟鍦板潃',
+ `payload` TEXT NOT NULL COMMENT '鍥炶皟璇锋眰浣�',
+ `http_status` INT COMMENT 'HTTP鐘舵�佺爜',
+ `response` TEXT COMMENT '鍝嶅簲鍐呭',
+ `success` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '鏄惁鎴愬姛',
+ `retry_count` INT NOT NULL DEFAULT 0 COMMENT '閲嶈瘯娆℃暟',
+ `last_retry_at` DATETIME COMMENT '鏈�鍚庨噸璇曟椂闂�',
+ `created_at` DATETIME NOT NULL COMMENT '鍒涘缓鏃堕棿',
+ INDEX `idx_order_id` (`order_id`),
+ INDEX `idx_success` (`success`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='涓氬姟鍥炶皟鏃ュ織琛�';
+
+-- 鎿嶄綔瀹¤琛�
+CREATE TABLE `pay_operation_audit` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '瀹¤ID',
+ `operator` VARCHAR(64) NOT NULL COMMENT '鎿嶄綔浜�',
+ `operation_type` VARCHAR(32) NOT NULL COMMENT '鎿嶄綔绫诲瀷',
+ `order_id` BIGINT COMMENT '璁㈠崟ID',
+ `transaction_id` BIGINT COMMENT '浜ゆ槗ID',
+ `params` TEXT COMMENT '鎿嶄綔鍙傛暟',
+ `approved` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '鏄惁閫氳繃',
+ `created_at` DATETIME NOT NULL COMMENT '鎿嶄綔鏃堕棿',
+ INDEX `idx_operator` (`operator`),
+ INDEX `idx_operation_type` (`operation_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鎿嶄綔瀹¤琛�';
+
+-- 瀵硅处浠诲姟琛�
+CREATE TABLE `pay_reconciliation_task` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '浠诲姟ID',
+ `task_date` DATE NOT NULL COMMENT '瀵硅处鏃ユ湡',
+ `status` VARCHAR(16) NOT NULL COMMENT '浠诲姟鐘舵��',
+ `total_count` INT NOT NULL DEFAULT 0 COMMENT '鎬昏鍗曟暟',
+ `success_count` INT NOT NULL DEFAULT 0 COMMENT '鎴愬姛鏁�',
+ `failed_count` INT NOT NULL DEFAULT 0 COMMENT '澶辫触鏁�',
+ `diff_count` INT NOT NULL DEFAULT 0 COMMENT '宸紓鏁�',
+ `fixed_count` INT NOT NULL DEFAULT 0 COMMENT '鑷姩淇鏁�',
+ `created_at` DATETIME NOT NULL COMMENT '鍒涘缓鏃堕棿',
+ `finished_at` DATETIME COMMENT '瀹屾垚鏃堕棿',
+ UNIQUE KEY `uk_task_date` (`task_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='瀵硅处浠诲姟琛�';
+
+-- 瀵硅处宸紓鏄庣粏琛�
+CREATE TABLE `pay_reconciliation_result` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '鏄庣粏ID',
+ `task_id` BIGINT NOT NULL COMMENT '浠诲姟ID',
+ `order_id` BIGINT NOT NULL COMMENT '璁㈠崟ID',
+ `transaction_id` BIGINT COMMENT '浜ゆ槗ID',
+ `local_status` VARCHAR(16) COMMENT '鏈湴鐘舵��',
+ `channel_status` VARCHAR(16) COMMENT '娓犻亾鐘舵��',
+ `diff_type` VARCHAR(32) NOT NULL COMMENT '宸紓绫诲瀷',
+ `fixed` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '鏄惁宸蹭慨澶�',
+ `note` VARCHAR(512) COMMENT '澶囨敞',
+ `created_at` DATETIME NOT NULL COMMENT '鍒涘缓鏃堕棿',
+ INDEX `idx_task_id` (`task_id`),
+ INDEX `idx_order_id` (`order_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='瀵硅处宸紓鏄庣粏琛�';
diff --git "a/dryad-payment/\351\233\206\346\210\220\344\275\277\347\224\250\346\214\207\345\215\227.md" "b/dryad-payment/\351\233\206\346\210\220\344\275\277\347\224\250\346\214\207\345\215\227.md"
new file mode 100644
index 0000000..90c5142
--- /dev/null
+++ "b/dryad-payment/\351\233\206\346\210\220\344\275\277\347\224\250\346\214\207\345\215\227.md"
@@ -0,0 +1,317 @@
+# 鏀粯妯″潡闆嗘垚浣跨敤鎸囧崡
+
+鏈敮浠樻ā鍧楁敮鎸佷袱绉嶄娇鐢ㄦ柟寮忥細**鐙珛杩愯** 鍜� **缁勪欢闆嗘垚**銆�
+
+---
+
+## 鏂瑰紡涓�锛氱嫭绔嬭繍琛�
+
+### 1. 缂栬瘧鎵撳寘
+
+```bash
+cd dryad-payment
+mvn clean package
+```
+
+缂栬瘧鍚庝細鐢熸垚涓や釜jar鏂囦欢锛�
+- `dryad-payment-1.0.0.jar` - 鏅�歫ar锛屼緵鍏朵粬椤圭洰渚濊禆
+- `dryad-payment-1.0.0-exec.jar` - 鍙墽琛宩ar锛岀敤浜庣嫭绔嬭繍琛�
+
+### 2. 杩愯鏈嶅姟
+
+```bash
+java -jar target/dryad-payment-1.0.0-exec.jar
+```
+
+### 3. 璁块棶鎺ュ彛
+
+鏈嶅姟鍚姩鍚庯紝鍙互鐩存帴璁块棶锛�
+- 鍋ュ悍妫�鏌�: `http://localhost:8080/api/health`
+- 寰俊鏀粯: `http://localhost:8080/api/pay/wechat/native`
+- 鏀粯瀹濇敮浠�: `http://localhost:8080/api/pay/alipay/precreate`
+
+---
+
+## 鏂瑰紡浜岋細浣滀负缁勪欢闆嗘垚鍒板叾浠栭」鐩�
+
+### 1. 瀹夎鍒版湰鍦癕aven浠撳簱
+
+棣栧厛灏嗘敮浠樻ā鍧楀畨瑁呭埌鏈湴浠撳簱锛�
+
+```bash
+cd dryad-payment
+mvn clean install
+```
+
+### 2. 鍦ㄤ富椤圭洰涓坊鍔犱緷璧�
+
+鍦ㄤ綘鐨勪富椤圭洰 `pom.xml` 涓坊鍔狅細
+
+```xml
+<dependencies>
+ <!-- 鏀粯妯″潡 -->
+ <dependency>
+ <groupId>com.ruoyi</groupId>
+ <artifactId>dryad-payment</artifactId>
+ <version>1.0.0</version>
+ </dependency>
+</dependencies>
+```
+
+### 3. 閰嶇疆鏀粯鍙傛暟
+
+鍦ㄤ富椤圭洰鐨� `application.yml` 涓坊鍔犳敮浠橀厤缃細
+
+```yaml
+# 鏀粯妯″潡閰嶇疆
+payment:
+ # 鍚敤鏀粯妯″潡
+ enabled: true
+
+ # 寰俊鏀粯閰嶇疆
+ wechat:
+ appId: wx70f6a7346ee842xx
+ mchId: 1573728xxx
+ mchKey: Xz0ClPK3f5sCeT6SGa1vpVmyUFcbpxxx
+ notifyUrl: https://api.966120.com/api/pay/notify/wechat
+ signType: MD5
+
+ # 鏀粯瀹濋厤缃�
+ alipay:
+ appId: 2021xxxxxxxxxxxxx
+ privateKey: MIIEvQIBADANBgkqhki...
+ alipayPublicKey: MIIBIjANBgkqhki...
+ serverUrl: https://openapi.alipay.com/gateway.do
+ notifyUrl: https://api.966120.com/api/pay/notify/alipay
+ signType: RSA2
+
+ # 涓氬姟鍥炶皟閰嶇疆
+ business:
+ callbackSignSecret: your-hmac-secret-key-here
+ callbackRetryMaxCount: 10
+ callbackRetryIntervals: 0,1,5,15,60
+
+ # 浜岀淮鐮侀厤缃�
+ qrcode:
+ size: 300
+ format: PNG
+
+ # 瀵硅处閰嶇疆
+ reconciliation:
+ enabled: true
+ cron: "0 0 2 * * ?"
+```
+
+### 4. 閰嶇疆鏁版嵁搴�
+
+鍦ㄤ富椤圭洰鐨� `application.yml` 涓厤缃暟鎹簱杩炴帴锛�
+
+```yaml
+spring:
+ datasource:
+ url: jdbc:mysql://localhost:3306/dryad_payment?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+ username: root
+ password: root
+```
+
+鎵ц鏀粯妯″潡鐨勬暟鎹簱鑴氭湰锛�
+
+```bash
+mysql -u root -p < dryad-payment/src/main/resources/sql/schema.sql
+```
+
+### 5. 鍚姩涓婚」鐩�
+
+鍚姩浣犵殑涓婚」鐩紝鎺у埗鍙颁細鏄剧ず锛�
+
+```
+=================================
+鏀粯妯″潡宸插惎鐢�
+=================================
+```
+
+### 6. 浣跨敤鏀粯鍔熻兘
+
+鏀粯妯″潡鐨勬墍鏈夋帴鍙d細鑷姩娉ㄥ唽鍒颁富椤圭洰涓紝鍙互鐩存帴璋冪敤锛�
+
+```java
+@RestController
+@RequestMapping("/order")
+public class OrderController {
+
+ @Autowired
+ private PaymentService paymentService;
+
+ @PostMapping("/pay")
+ public AjaxResult createPayment(@RequestBody OrderDTO order) {
+ PaymentRequest request = new PaymentRequest();
+ request.setBizOrderId(order.getOrderNo());
+ request.setAmount(order.getAmount());
+ request.setSubject(order.getSubject());
+ request.setCallbackUrl("https://your-domain.com/order/notify");
+
+ PaymentResponse response = paymentService.createWechatNativePayment(request);
+ return AjaxResult.success(response);
+ }
+}
+```
+
+---
+
+## 鍙�夐厤缃�
+
+### 绂佺敤鏀粯妯″潡
+
+濡傛灉鏌愪釜鐜涓嶉渶瑕佹敮浠樺姛鑳斤紝鍙互閫氳繃閰嶇疆绂佺敤锛�
+
+```yaml
+payment:
+ enabled: false # 绂佺敤鏀粯妯″潡
+```
+
+### 鑷畾涔夋帴鍙h矾寰�
+
+濡傛灉闇�瑕佷慨鏀规帴鍙h矾寰勶紝鍙互鍦ㄤ富椤圭洰涓厤缃細
+
+```yaml
+server:
+ servlet:
+ context-path: /api # 涓婚」鐩粺涓�鍓嶇紑
+```
+
+鏀粯鎺ュ彛浼氳嚜鍔ㄦ坊鍔犳鍓嶇紑锛屼緥濡傦細
+- `/api/api/pay/wechat/native`
+
+### 瑕嗙洊榛樿閰嶇疆
+
+涓婚」鐩殑閰嶇疆浼氳鐩栨敮浠樻ā鍧楃殑榛樿閰嶇疆锛屼緥濡備慨鏀圭鍙o細
+
+```yaml
+server:
+ port: 9090 # 涓婚」鐩鍙�
+```
+
+---
+
+## 鎵撳寘璇存槑
+
+### 鐢熸垚鐨勬枃浠�
+
+鎵ц `mvn clean package` 鍚庝細鐢熸垚锛�
+
+1. **dryad-payment-1.0.0.jar**
+ - 鏅�歫ar鍖�
+ - 鐢ㄤ簬琚叾浠栭」鐩緷璧�
+ - 涓嶅寘鍚玈pring Boot鍚姩鍣�
+
+2. **dryad-payment-1.0.0-exec.jar**
+ - 鍙墽琛宩ar鍖�
+ - 鐢ㄤ簬鐙珛杩愯
+ - 鍖呭惈鎵�鏈変緷璧�
+
+3. **dryad-payment-1.0.0-sources.jar**
+ - 婧愮爜jar鍖�
+ - 渚夸簬寮�鍙戞椂鏌ョ湅婧愮爜
+
+### Maven鍛戒护
+
+```bash
+# 缂栬瘧
+mvn clean compile
+
+# 鎵撳寘锛堢敓鎴恓ar锛�
+mvn clean package
+
+# 瀹夎鍒版湰鍦颁粨搴�
+mvn clean install
+
+# 璺宠繃娴嬭瘯鎵撳寘
+mvn clean package -DskipTests
+
+# 閮ㄧ讲鍒拌繙绋嬩粨搴擄紙闇�閰嶇疆锛�
+mvn clean deploy
+```
+
+---
+
+## 鏈�浣冲疄璺�
+
+### 1. 閰嶇疆绠$悊
+
+寤鸿灏嗘晱鎰熼厤缃紙瀵嗛挜绛夛級鏀惧湪閰嶇疆涓績鎴栫幆澧冨彉閲忎腑锛�
+
+```yaml
+payment:
+ wechat:
+ mchKey: ${WECHAT_MCH_KEY} # 浠庣幆澧冨彉閲忚鍙�
+ alipay:
+ privateKey: ${ALIPAY_PRIVATE_KEY}
+```
+
+### 2. 澶氱幆澧冮厤缃�
+
+鍒涘缓涓嶅悓鐜鐨勯厤缃枃浠讹細
+
+- `application-dev.yml` - 寮�鍙戠幆澧冿紙娌欑锛�
+- `application-prod.yml` - 鐢熶骇鐜
+
+```yaml
+# application-dev.yml
+payment:
+ alipay:
+ serverUrl: https://openapi.alipaydev.com/gateway.do # 娌欑鐜
+```
+
+### 3. 鏃ュ織閰嶇疆
+
+鍦ㄤ富椤圭洰涓彲浠ュ崟鐙厤缃敮浠樻ā鍧楃殑鏃ュ織绾у埆锛�
+
+```yaml
+logging:
+ level:
+ com.ruoyi.payment: DEBUG # 鏀粯妯″潡璇︾粏鏃ュ織
+```
+
+### 4. 鏁版嵁搴撻殧绂�
+
+濡傛灉涓婚」鐩拰鏀粯妯″潡浣跨敤涓嶅悓鏁版嵁搴擄紝闇�瑕侀厤缃鏁版嵁婧愩��
+
+---
+
+## 甯歌闂
+
+### Q1: 闆嗘垚鍚庢彁绀烘壘涓嶅埌Bean锛�
+
+**A**: 纭繚涓婚」鐩殑鍚姩绫诲寘璺緞鍦� `com.ruoyi` 鎴栧叾瀛愬寘涓嬶紝鎴栬�呮墜鍔ㄦ坊鍔犳壂鎻忥細
+
+```java
+@SpringBootApplication
+@ComponentScan(basePackages = {"com.ruoyi.main", "com.ruoyi.payment"})
+public class MainApplication {
+ // ...
+}
+```
+
+### Q2: 鎺ュ彛璺緞鍐茬獊锛�
+
+**A**: 濡傛灉涓婚」鐩篃鏈� `/api/pay` 璺緞锛屽缓璁慨鏀规敮浠樻ā鍧楃殑 `@RequestMapping` 璺緞銆�
+
+### Q3: 閰嶇疆涓嶇敓鏁堬紵
+
+**A**: 妫�鏌ラ厤缃枃浠舵牸寮忥紝纭繚锛�
+- YAML缂╄繘姝g‘锛�2涓┖鏍硷級
+- 灞炴�у悕绉版纭�
+- 閰嶇疆鏂囦欢鍚嶇О姝g‘锛坅pplication.yml锛�
+
+### Q4: 鏁版嵁搴撹〃鍐茬獊锛�
+
+**A**: 鏀粯妯″潡鐨勮〃閮戒互 `pay_` 寮�澶达紝涓�鑸笉浼氬啿绐併�傚闇�淇敼琛ㄥ悕锛岃淇敼瀹炰綋绫荤殑 `@TableName` 娉ㄨВ銆�
+
+---
+
+## 鎶�鏈敮鎸�
+
+濡傛湁闂锛岃鏌ョ湅锛�
+- 鎺ュ彛鏂囨。: `API鎺ュ彛鏂囨。.md`
+- 璁捐鏂囨。: `doc/璁捐鏂规.md`
+- 婧愮爜娉ㄩ噴: 浠g爜涓寘鍚缁嗘敞閲�
diff --git a/pom.xml b/pom.xml
index 1978da3..8470f4d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -242,6 +242,7 @@
<module>ruoyi-quartz</module>
<module>ruoyi-generator</module>
<module>ruoyi-common</module>
+ <module>dryad-payment</module>
</modules>
<packaging>pom</packaging>
diff --git "a/prd/\345\210\206\345\205\254\345\217\270\351\273\230\350\256\244\345\207\272\345\217\221\345\234\260\351\205\215\347\275\256\345\212\237\350\203\275\350\257\264\346\230\216.md" "b/prd/\345\210\206\345\205\254\345\217\270\351\273\230\350\256\244\345\207\272\345\217\221\345\234\260\351\205\215\347\275\256\345\212\237\350\203\275\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..7079032
--- /dev/null
+++ "b/prd/\345\210\206\345\205\254\345\217\270\351\273\230\350\256\244\345\207\272\345\217\221\345\234\260\351\205\215\347\275\256\345\212\237\350\203\275\350\257\264\346\230\216.md"
@@ -0,0 +1,189 @@
+# 鍒嗗叕鍙搁粯璁ゅ嚭鍙戝湴閰嶇疆鍔熻兘璇存槑
+
+## 鍔熻兘姒傝堪
+
+涓哄垱寤鸿浆杩愪换鍔¢〉闈㈠鍔犱簡鍑哄彂鍦拌緭鍏ュ姛鑳斤紝鏀寔浠庡垎鍏徃閰嶇疆涓嚜鍔ㄥ姞杞介粯璁ゅ嚭鍙戝湴锛岀敤鎴蜂篃鍙互鎵嬪姩淇敼銆�
+
+## 鏁版嵁搴撳彉鏇�
+
+### sys_dept 琛ㄦ柊澧炲瓧娈�
+
+鍦� `sys_dept` 琛ㄤ腑鏂板浜�3涓瓧娈电敤浜庡瓨鍌ㄥ垎鍏徃鐨勯粯璁ゅ嚭杞﹀湴鍧�锛�
+
+| 瀛楁鍚� | 绫诲瀷 | 璇存槑 |
+|--------|------|------|
+| `departure_address` | VARCHAR(500) | 榛樿鍑鸿溅鍦板潃 |
+| `departure_longitude` | DECIMAL(10, 6) | 鍑鸿溅鍦板潃缁忓害 |
+| `departure_latitude` | DECIMAL(10, 6) | 鍑鸿溅鍦板潃绾害 |
+
+**SQL鑴氭湰浣嶇疆**: `sql/sys_dept_add_departure_fields.sql`
+
+## 鍚庣淇敼
+
+### 1. SysDept 瀹炰綋绫� (SysDept.java)
+
+鏂板灞炴�э細
+```java
+private String departureAddress;
+private java.math.BigDecimal departureLongitude;
+private java.math.BigDecimal departureLatitude;
+```
+
+### 2. SysDeptMapper.xml
+
+- 鏇存柊 `resultMap`锛屽鍔犳柊瀛楁鏄犲皠
+- 鏇存柊 `selectDeptVo`锛屾煡璇㈡椂鍖呭惈鏂板瓧娈�
+- 鏇存柊 `insertDept`锛屾彃鍏ユ椂鏀寔鏂板瓧娈�
+- 鏇存柊 `updateDept`锛屾洿鏂版椂鏀寔鏂板瓧娈�
+
+## 鍓嶇淇敼
+
+### 1. OrganizationSelector 缁勪欢
+
+淇敼 `emitChange` 鏂规硶锛屽湪閫夋嫨褰掑睘鏈烘瀯鏃惰繑鍥炲嚭鍙戝湴淇℃伅锛�
+
+```javascript
+this.$emit('change', {
+ deptId: organization.deptId,
+ deptName: organization.deptName,
+ serviceOrderClass: organization.serviceOrderClass || '',
+ region: region,
+ // 鍑哄彂鍦颁俊鎭�
+ departureAddress: organization.departureAddress || '',
+ departureLongitude: organization.departureLongitude || null,
+ departureLatitude: organization.departureLatitude || null
+})
+```
+
+### 2. create-emergency.vue 椤甸潰
+
+#### 鏂板鏁版嵁瀛楁
+
+```javascript
+data() {
+ return {
+ departureAddress: '', // 鍑哄彂鍦板湴鍧�
+ departureLongitude: null, // 鍑哄彂鍦扮粡搴�
+ departureLatitude: null, // 鍑哄彂鍦扮含搴�
+ // ... 鍏朵粬瀛楁
+ }
+}
+```
+
+#### 褰掑睘鏈烘瀯鍙樺寲澶勭悊
+
+鍦� `onOrganizationChange` 鏂规硶涓嚜鍔ㄥ~鍏呭嚭鍙戝湴锛�
+
+```javascript
+onOrganizationChange(orgData) {
+ this.selectedOrganizationId = orgData.deptId
+ this.selectedOrganizationServiceOrderClass = orgData.serviceOrderClass
+ this.selectedRegion = orgData.region
+
+ // 鑷姩濉厖鍑哄彂鍦颁俊鎭�
+ this.departureAddress = orgData.departureAddress || ''
+ this.departureLongitude = orgData.departureLongitude || null
+ this.departureLatitude = orgData.departureLatitude || null
+}
+```
+
+#### 鐣岄潰鏂板鍑哄彂鍦拌緭鍏ユ
+
+鍦ㄥ綊灞炴満鏋勯�夋嫨鍣ㄤ箣鍚庡鍔狅細
+
+```vue
+<view class="form-item">
+ <view class="form-label">鍑哄彂鍦�</view>
+ <input
+ class="form-input"
+ placeholder="璇疯緭鍏ュ嚭鍙戝湴鍦板潃"
+ v-model="departureAddress"
+ />
+ <view class="form-tip" v-if="departureAddress">
+ <text>鎻愮ず锛氬彲淇敼榛樿鍑哄彂鍦板湴鍧�</text>
+ </view>
+</view>
+```
+
+#### 鎻愪氦鏁版嵁澶勭悊
+
+鍦� `buildSubmitData` 鏂规硶涓細
+
+```javascript
+const submitData = {
+ // 浼樺厛浣跨敤鑷畾涔夌殑鍑哄彂鍦帮紝濡傛灉涓虹┖鍒欎娇鐢ㄨ浆鍑哄尰闄㈠湴鍧�
+ departureAddress: this.departureAddress || this.taskForm.hospitalOut.address || '',
+ // ... 鍏朵粬瀛楁
+}
+
+// 鍑哄彂鍦癎PS鍧愭爣锛堜紭鍏堜娇鐢ㄨ嚜瀹氫箟鐨勫嚭鍙戝湴鍧愭爣锛�
+if (this.departureLongitude && this.departureLatitude) {
+ submitData.departureLongitude = this.departureLongitude
+ submitData.departureLatitude = this.departureLatitude
+} else if (this.addressCoordinates.hospitalOutAddress) {
+ // 鍏滃簳锛氫娇鐢ㄨ浆鍑哄尰闄㈢殑鍧愭爣
+ submitData.departureLongitude = this.addressCoordinates.hospitalOutAddress.lng
+ submitData.departureLatitude = this.addressCoordinates.hospitalOutAddress.lat
+}
+```
+
+## 浣跨敤娴佺▼
+
+### 1. 閰嶇疆鍒嗗叕鍙搁粯璁ゅ嚭鍙戝湴
+
+鍦ㄥ悗鍙扮鐞嗙郴缁熺殑閮ㄩ棬绠$悊涓紝涓哄悇鍒嗗叕鍙搁厤缃粯璁ゅ嚭杞﹀湴鍧�鍙奊PS鍧愭爣锛�
+
+1. 杩涘叆"绯荤粺绠$悊" -> "閮ㄩ棬绠$悊"
+2. 閫夋嫨闇�瑕侀厤缃殑鍒嗗叕鍙革紙parent_id=100鐨勯儴闂級
+3. 鐐瑰嚮"淇敼"
+4. 濉啓浠ヤ笅淇℃伅锛�
+ - **榛樿鍑鸿溅鍦板潃**: 濡�"骞垮窞甯傚ぉ娌冲尯XXX璺疿XX鍙�"
+ - **鍑鸿溅鍦板潃缁忓害**: 濡� 113.123456
+ - **鍑鸿溅鍦板潃绾害**: 濡� 23.123456
+
+### 2. 鍒涘缓杞繍浠诲姟鏃朵娇鐢�
+
+1. 鍦ˋPP绔墦寮�"鍒涘缓杞繍浠诲姟"椤甸潰
+2. 閫夋嫨"褰掑睘鏈烘瀯"锛堝垎鍏徃锛�
+3. 绯荤粺鑷姩濉厖璇ュ垎鍏徃閰嶇疆鐨勯粯璁ゅ嚭鍙戝湴
+4. 鐢ㄦ埛鍙互鎵嬪姩淇敼鍑哄彂鍦板湴鍧�
+5. 鎻愪氦鏃讹紝浼樺厛浣跨敤鑷畾涔夌殑鍑哄彂鍦帮紝濡備负绌哄垯浣跨敤杞嚭鍖婚櫌鍦板潃
+
+## 浼樺厛绾ц鍒�
+
+鍑哄彂鍦扮殑浼樺厛绾т粠楂樺埌浣庝负锛�
+
+1. **鐢ㄦ埛鎵嬪姩淇敼鐨勫嚭鍙戝湴** - 鏈�楂樹紭鍏堢骇
+2. **鍒嗗叕鍙搁厤缃殑榛樿鍑哄彂鍦�** - 閫夋嫨褰掑睘鏈烘瀯鏃惰嚜鍔ㄥ~鍏�
+3. **杞嚭鍖婚櫌鍦板潃** - 濡傛灉鍓嶄袱鑰呴兘涓虹┖锛屼娇鐢ㄨ浆鍑哄尰闄㈠湴鍧�
+
+GPS鍧愭爣鐨勪紭鍏堢骇锛�
+
+1. **鍒嗗叕鍙搁厤缃殑鍑哄彂鍦板潗鏍�** - 濡傛灉鏈夐厤缃�
+2. **杞嚭鍖婚櫌鍦板潃鐨勫潗鏍�** - 鍏滃簳鏂规
+
+## 娉ㄦ剰浜嬮」
+
+1. **鏁版嵁搴撳崌绾�**: 棣栨閮ㄧ讲闇�瑕佹墽琛� `sql/sys_dept_add_departure_fields.sql` 鑴氭湰
+2. **鏉冮檺閰嶇疆**: 纭繚閮ㄩ棬绠$悊鐨勭紪杈戞潈闄愬寘鍚柊澧炵殑瀛楁
+3. **鍏煎鎬�**: 鏈厤缃嚭鍙戝湴鐨勫垎鍏徃锛岀郴缁熶細鑷姩浣跨敤杞嚭鍖婚櫌鍦板潃锛屼笉褰卞搷鐜版湁鍔熻兘
+4. **鏁版嵁鏍¢獙**: 缁忕含搴﹀瓧娈垫敮鎸丯ULL锛屽彲浠ュ彧濉啓鍦板潃涓嶅~鍐欏潗鏍�
+
+## 鐩稿叧鏂囦欢
+
+### 鏁版嵁搴撹剼鏈�
+- `sql/sys_dept_add_departure_fields.sql`
+
+### 鍚庣鏂囦欢
+- `ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java`
+- `ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml`
+
+### 鍓嶇鏂囦欢
+- `app/components/OrganizationSelector.vue`
+- `app/pages/task/create-emergency.vue`
+
+## 鍚庣画浼樺寲寤鸿
+
+1. 鍙湪鍚庡彴绠$悊绯荤粺鐨勯儴闂ㄧ紪杈戦〉闈㈠鍔犲湴鍥鹃�夌偣鍔熻兘锛屾柟渚块厤缃粡绾害
+2. 鍙鍔犲嚭鍙戝湴鐨勫湴鍧�寤鸿鍔熻兘锛岀被浼煎尰闄㈠湴鍧�杈撳叆
+3. 鍙�冭檻涓烘瘡涓垎鍏徃閰嶇疆澶氫釜甯哥敤鍑哄彂鍦帮紝渚涚敤鎴峰揩閫熼�夋嫨
diff --git "a/prd/\346\224\257\344\273\230\344\277\241\346\201\257\345\217\214\345\220\221\345\220\214\346\255\245\345\212\237\350\203\275\350\257\264\346\230\216.md" "b/prd/\346\224\257\344\273\230\344\277\241\346\201\257\345\217\214\345\220\221\345\220\214\346\255\245\345\212\237\350\203\275\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..54a7d72
--- /dev/null
+++ "b/prd/\346\224\257\344\273\230\344\277\241\346\201\257\345\217\214\345\220\221\345\220\214\346\255\245\345\212\237\350\203\275\350\257\264\346\230\216.md"
@@ -0,0 +1,276 @@
+# 鏀粯淇℃伅鍙屽悜鍚屾鍔熻兘璇存槑
+
+## 鍔熻兘姒傝堪
+
+瀹炵幇鏂扮郴缁� `sys_task_payment` 琛ㄤ笌鏃х郴缁� `PaidMoney` 琛ㄤ箣闂寸殑鏀粯淇℃伅鍙屽悜鍚屾锛岀‘淇濅袱涓郴缁熺殑鏀粯鏁版嵁淇濇寔涓�鑷淬��
+
+## 鎶�鏈灦鏋�
+
+### 鏍稿績瀹炰綋
+
+#### 1. SysTaskPayment锛堟柊绯荤粺鏀粯璁板綍锛�
+- **琛ㄥ悕**: `sys_task_payment`
+- **鏂板瀛楁**:
+ - `pid`: 鏃х郴缁熸敮浠樿褰旾D锛圥aidMoney.id锛�
+ - `sync_status`: 鍚屾鐘舵�侊紙0鏈悓姝ワ紝1鍚屾涓紝2鍚屾鎴愬姛锛�3鍚屾澶辫触锛�
+ - `sync_time`: 鍚屾鏃堕棿
+
+#### 2. PaidMoney锛堟棫绯荤粺鏀粯璁板綍锛�
+- **琛ㄥ悕**: `PaidMoney`
+- **鍏抽敭瀛楁**:
+ - `id`: 涓婚敭
+ - `ServiceOrdIDDt`: 鏈嶅姟璁㈠崟ID
+ - `DispatchOrdIDDt`: 璋冨害璁㈠崟ID
+ - `PaidMoney`: 鏀粯閲戦
+ - `PaidMoneyType`: 鏀粯绫诲瀷
+ - `PaidMoneyMono`: 鏀粯鍗曞彿
+ - `PaidMoneyTime`: 鏀粯鏃堕棿
+ - `PaidMoneyOaID`: 鏀粯浜篛AID
+
+### 鍏抽敭鍏宠仈
+
+閫氳繃 `SysTaskEmergency` 琛ㄨ幏鍙栦换鍔′笌鏃х郴缁熻鍗曠殑鏄犲皠鍏崇郴锛�
+- `legacy_service_ord_id` 鈫� `ServiceOrdIDDt`
+- `legacy_dispatch_ord_id` 鈫� `DispatchOrdIDDt`
+
+## 鍚屾閫昏緫
+
+### 1. 鏂扮郴缁� 鈫� 鏃х郴缁熷悓姝�
+
+#### 瑙﹀彂鏃舵満
+- 鏂扮郴缁熸敮浠樻垚鍔熷悗鑷姩瑙﹀彂
+- 鍖呮嫭锛氱幇閲戞敮浠樸�佹寕璐︺�佸井淇℃敮浠樻垚鍔熴�佹敮浠樺疂鏀粯鎴愬姛
+
+#### 鍚屾娴佺▼
+1. 鏀粯鎴愬姛鍚庯紝璋冪敤 `PaymentSyncService.syncPaymentToLegacy(paymentId)`
+2. 鏌ヨ鏂扮郴缁熸敮浠樿褰曞拰浠诲姟淇℃伅
+3. 鑾峰彇 `ServiceOrdIDDt` 鍜� `DispatchOrdIDDt`
+4. 杞崲鏀粯鏂瑰紡锛堟柊绯荤粺 鈫� 鏃х郴缁燂級
+5. 鏋勫缓 `PaidMoney` 瀵硅薄骞舵彃鍏ユ棫绯荤粺
+6. 鏇存柊鏂扮郴缁熸敮浠樿褰曠殑 `pid`銆乣sync_status`銆乣sync_time`
+
+#### 鏀粯鏂瑰紡鏄犲皠锛堟柊 鈫� 鏃э級
+| 鏂扮郴缁熸敮浠樻柟寮� | 鏃х郴缁熸敮浠樼被鍨� | 璇存槑 |
+|--------------|--------------|------|
+| CASH | 1 | 鐜伴噾 |
+| ON_ACCOUNT | 6 | 鎸傝处 |
+| WECHAT | 3 | 寰俊鏀粯 |
+| ALIPAY | 4 | 鏀粯瀹� |
+
+#### 鏁版嵁鏄犲皠
+
+```java
+PaidMoney.PaidMoneyClass = "FI" // 榛樿FI
+PaidMoney.ServiceOrdIDDt = emergency.legacyServiceOrdId
+PaidMoney.DispatchOrdIDDt = emergency.legacyDispatchOrdId
+PaidMoney.PaidMoney = payment.settlementAmount
+PaidMoney.PaidMoneyType = convertPaymentMethodToLegacy(payment.paymentMethod)
+PaidMoney.PaidMoneyMono = payment.tradeNo 鎴� payment.outTradeNo
+PaidMoney.PaidMoneyTime = payment.payTime
+PaidMoney.PaidMoneyOaID = user.oaUserId
+PaidMoney.PaidMoneyUnitID = 0 // 榛樿0
+PaidMoney.PaidMoneyAPCheck = 1 // 宸茬‘璁�
+PaidMoney.PaidMoneyAPTime = now()
+```
+
+### 2. 鏃х郴缁� 鈫� 鏂扮郴缁熷悓姝�
+
+#### 瑙﹀彂鏃舵満
+- 瀹氭椂浠诲姟锛堝緟瀹炵幇锛�
+- 鎵嬪姩璋冪敤 `PaymentSyncService.syncPaymentFromLegacy(paidMoneyId)`
+
+#### 鍚屾娴佺▼
+1. 鏌ヨ鏃х郴缁� `PaidMoney` 璁板綍
+2. 妫�鏌ユ槸鍚﹀凡鍚屾锛堥�氳繃 `pid` 瀛楁锛�
+3. 鏍规嵁 `ServiceOrdIDDt` 鏌ヨ鏂扮郴缁熶换鍔�
+4. 杞崲鏀粯鏂瑰紡锛堟棫绯荤粺 鈫� 鏂扮郴缁燂級
+5. 鏋勫缓 `SysTaskPayment` 瀵硅薄骞舵彃鍏ユ柊绯荤粺
+6. 璁剧疆 `pid`銆乣sync_status=2`銆乣sync_time`
+
+#### 鏀粯鏂瑰紡鏄犲皠锛堟棫 鈫� 鏂帮級
+| 鏃х郴缁熸敮浠樼被鍨� | 鏂扮郴缁熸敮浠樻柟寮� | 璇存槑 |
+|--------------|--------------|------|
+| 1 | CASH | 鐜伴噾 |
+| 6, 7 | ON_ACCOUNT | 鎸傝处銆佹槗鍖婚�氭寕璐� |
+| 3 | WECHAT | 寰俊鏀粯 |
+| 4 | ALIPAY | 鏀粯瀹� |
+
+#### 鏁版嵁鏄犲皠
+
+```java
+SysTaskPayment.taskId = task.taskId
+SysTaskPayment.totalAmount = paidMoney.paidMoney
+SysTaskPayment.settlementAmount = paidMoney.paidMoney
+SysTaskPayment.paymentMethod = convertPaymentMethodFromLegacy(paidMoney.paidMoneyType)
+SysTaskPayment.payStatus = "PAID"
+SysTaskPayment.payTime = paidMoney.paidMoneyTime
+SysTaskPayment.outTradeNo = taskCode + "-" + paidMoneyId
+SysTaskPayment.tradeNo = paidMoney.paidMoneyMono
+SysTaskPayment.pid = paidMoneyId
+SysTaskPayment.syncStatus = 2
+SysTaskPayment.syncTime = now()
+```
+
+## 鏍稿績浠g爜瀹炵幇
+
+### 鏂囦欢娓呭崟
+
+#### 鏂板鏂囦欢
+1. **瀹炰綋绫�**
+ - `PaidMoney.java` - 鏃х郴缁熸敮浠樿褰曞疄浣�
+
+2. **Mapper鎺ュ彛**
+ - `PaidMoneyMapper.java` - 鏃х郴缁熸敮浠樿褰昅apper鎺ュ彛
+
+3. **Mapper XML**
+ - `PaidMoneyMapper.xml` - 鏃х郴缁熸敮浠樿褰昐QL鏄犲皠
+
+4. **Service鎺ュ彛**
+ - `IPaymentSyncService.java` - 鏀粯鍚屾鏈嶅姟鎺ュ彛
+
+5. **Service瀹炵幇**
+ - `PaymentSyncServiceImpl.java` - 鏀粯鍚屾鏈嶅姟瀹炵幇
+
+6. **鏁版嵁搴撹剼鏈�**
+ - `payment_sync_update.sql` - 鏁版嵁琛ㄦ洿鏂拌剼鏈�
+
+#### 淇敼鏂囦欢
+1. **瀹炰綋绫�**
+ - `SysTaskPayment.java` - 鏂板 pid銆乻yncStatus銆乻yncTime 瀛楁
+
+2. **Mapper鎺ュ彛**
+ - `SysTaskPaymentMapper.java` - 鏂板 selectByPid銆乽pdateSyncInfo 鏂规硶
+
+3. **Mapper XML**
+ - `SysTaskPaymentMapper.xml` - 鏇存柊鏄犲皠鍜屾柊澧炴煡璇㈡柟娉�
+
+4. **Service瀹炵幇**
+ - `SysTaskPaymentServiceImpl.java` - 鏀粯鎴愬姛鍚庤Е鍙戝悓姝�
+
+## 鏁版嵁婧愰厤缃�
+
+浣跨敤 `@DataSource` 娉ㄨВ鍒囨崲鏁版嵁婧愶細
+
+```java
+// 璁块棶鏂扮郴缁熸暟鎹簱锛堥粯璁わ級
+public void processNewSystem() {
+ // 榛樿浣跨敤涓绘暟鎹簮
+}
+
+// 璁块棶鏃х郴缁熸暟鎹簱
+@DataSource(DataSourceType.SQLSERVER)
+public void processLegacySystem() {
+ // 浣跨敤SQL Server鏁版嵁婧�
+}
+```
+
+## 鍚屾鐘舵�佺鐞�
+
+### sync_status 鐘舵�佸��
+- **0 - 鏈悓姝�**: 鏂板垱寤虹殑鏀粯璁板綍锛屽皻鏈悓姝ュ埌鏃х郴缁�
+- **1 - 鍚屾涓�**: 姝e湪鎵ц鍚屾鎿嶄綔
+- **2 - 鍚屾鎴愬姛**: 宸叉垚鍔熷悓姝ュ埌鏃х郴缁�
+- **3 - 鍚屾澶辫触**: 鍚屾杩囩▼涓彂鐢熼敊璇�
+
+### 鐘舵�佹祦杞�
+```
+鏂版敮浠樿褰� 鈫� 0(鏈悓姝�) 鈫� 1(鍚屾涓�) 鈫� 2(鍚屾鎴愬姛) 鎴� 3(鍚屾澶辫触)
+ 鈫�
+ 鍙噸璇�
+```
+
+## 寮傚父澶勭悊
+
+### 鍚屾澶辫触鍦烘櫙
+1. 浠诲姟鏈悓姝ュ埌鏃х郴缁燂紙缂哄皯 ServiceOrdID/DispatchOrdID锛�
+2. 鏃х郴缁熸暟鎹簱杩炴帴澶辫触
+3. 鏁版嵁鎻掑叆鍐茬獊鎴栫害鏉熻繚鍙�
+4. 缃戠粶寮傚父
+
+### 澶勭悊绛栫暐
+1. 鍚屾澶辫触鏃舵爣璁� `sync_status=3`
+2. 璁板綍璇︾粏閿欒鏃ュ織
+3. 涓嶅奖鍝嶄富涓氬姟娴佺▼锛堝紓姝ュ悓姝ワ級
+4. 鍙�氳繃瀹氭椂浠诲姟鎴栨墜鍔ㄨЕ鍙戦噸璇�
+
+## 浣跨敤绀轰緥
+
+### 鎵嬪姩瑙﹀彂鍚屾
+
+```java
+@Autowired
+private IPaymentSyncService paymentSyncService;
+
+// 鍚屾鍗曟潯鏀粯璁板綍鍒版棫绯荤粺
+boolean success = paymentSyncService.syncPaymentToLegacy(paymentId);
+
+// 浠庢棫绯荤粺鍚屾鏀粯璁板綍
+boolean success = paymentSyncService.syncPaymentFromLegacy(paidMoneyId);
+
+// 鎵归噺鍚屾锛堝緟瀹炵幇锛�
+int count = paymentSyncService.batchSyncPaymentToLegacy();
+int count = paymentSyncService.batchSyncPaymentFromLegacy();
+```
+
+## 鏁版嵁搴撴洿鏂�
+
+鎵ц SQL 鑴氭湰锛�
+```sql
+source sql/payment_sync_update.sql
+```
+
+鎴栨墜鍔ㄦ墽琛岋細
+```sql
+ALTER TABLE sys_task_payment ADD COLUMN pid BIGINT COMMENT '鏃х郴缁熸敮浠樿褰旾D(PaidMoney.id)';
+ALTER TABLE sys_task_payment ADD COLUMN sync_status INT DEFAULT 0 COMMENT '鍚屾鐘舵�侊細0鏈悓姝ワ紝1鍚屾涓紝2鍚屾鎴愬姛锛�3鍚屾澶辫触';
+ALTER TABLE sys_task_payment ADD COLUMN sync_time DATETIME COMMENT '鍚屾鏃堕棿';
+CREATE INDEX idx_pid ON sys_task_payment(pid);
+CREATE INDEX idx_sync_status ON sys_task_payment(sync_status);
+```
+
+## 鍚庣画浼樺寲寤鸿
+
+1. **鎵归噺鍚屾瀹炵幇**
+ - 瀹炵幇瀹氭椂浠诲姟鎵归噺鍚屾鏈悓姝ョ殑鏀粯璁板綍
+ - 瀹炵幇浠庢棫绯荤粺瀹氭湡鎷夊彇鏂板鏀粯璁板綍
+
+2. **閲嶈瘯鏈哄埗**
+ - 瀵瑰悓姝ュけ璐ョ殑璁板綍瀹炵幇鑷姩閲嶈瘯
+ - 璁剧疆鏈�澶ч噸璇曟鏁板拰閲嶈瘯闂撮殧
+
+3. **鐩戞帶鍛婅**
+ - 鐩戞帶鍚屾澶辫触鐜�
+ - 鍚屾寤惰繜鍛婅
+ - 鏁版嵁涓�鑷存�ф牎楠�
+
+4. **鎬ц兘浼樺寲**
+ - 鑰冭檻浣跨敤娑堟伅闃熷垪寮傛澶勭悊
+ - 鎵归噺鎿嶄綔浼樺寲鏁版嵁搴撴�ц兘
+
+## 娉ㄦ剰浜嬮」
+
+1. **鍓嶇疆鏉′欢**: 浠诲姟蹇呴』鍏堝悓姝ュ埌鏃х郴缁燂紙鏈� ServiceOrdID 鍜� DispatchOrdID锛�
+2. **骞傜瓑鎬�**: 閲嶅鍚屾浼氳妫�娴嬪苟璺宠繃
+3. **浜嬪姟澶勭悊**: 鍚屾鎿嶄綔浣跨敤鐙珛浜嬪姟锛屽け璐ヤ笉褰卞搷涓讳笟鍔�
+4. **鏁版嵁涓�鑷存��**: 閫氳繃 pid 瀛楁寤虹珛鍙屽悜鍏宠仈锛岄伩鍏嶉噸澶嶅悓姝�
+5. **鏀粯鏂瑰紡鏄犲皠**: 纭繚鏂版棫绯荤粺鏀粯鏂瑰紡姝g‘鏄犲皠
+
+## 娴嬭瘯寤鸿
+
+1. **鍗曞厓娴嬭瘯**
+ - 娴嬭瘯鏀粯鏂瑰紡杞崲閫昏緫
+ - 娴嬭瘯鏁版嵁鏄犲皠姝g‘鎬�
+ - 娴嬭瘯寮傚父澶勭悊
+
+2. **闆嗘垚娴嬭瘯**
+ - 娴嬭瘯瀹屾暣鍚屾娴佺▼
+ - 娴嬭瘯骞傜瓑鎬�
+ - 娴嬭瘯澶辫触閲嶈瘯
+
+3. **鏁版嵁鏍¢獙**
+ - 瀵规瘮鏂版棫绯荤粺鏀粯鏁版嵁
+ - 楠岃瘉閲戦銆佹椂闂淬�佺姸鎬佷竴鑷存��
+
+---
+鏇存柊鏃堕棿: 2025-01-15
+鐗堟湰: v1.0
diff --git "a/prd/\351\231\204\345\212\240\350\264\271\347\224\250\345\217\214\345\220\221\345\220\214\346\255\245\345\212\237\350\203\275\350\257\264\346\230\216.md" "b/prd/\351\231\204\345\212\240\350\264\271\347\224\250\345\217\214\345\220\221\345\220\214\346\255\245\345\212\237\350\203\275\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..06b6860
--- /dev/null
+++ "b/prd/\351\231\204\345\212\240\350\264\271\347\224\250\345\217\214\345\220\221\345\220\214\346\255\245\345\212\237\350\203\275\350\257\264\346\230\216.md"
@@ -0,0 +1,285 @@
+# 闄勫姞璐圭敤鍙屽悜鍚屾鍔熻兘璇存槑
+
+## 鍔熻兘姒傝堪
+
+瀹炵幇鏂扮郴缁� `sys_task_additional_fee` 琛ㄤ笌鏃х郴缁� `PaidMoney_Add` 琛ㄤ箣闂寸殑闄勫姞璐圭敤鍙屽悜鍚屾锛岀‘淇濅袱涓郴缁熺殑闄勫姞璐圭敤鏁版嵁淇濇寔涓�鑷淬��
+
+## 鎶�鏈灦鏋�
+
+### 鏍稿績瀹炰綋
+
+#### 1. SysTaskAdditionalFee锛堟柊绯荤粺闄勫姞璐圭敤璁板綍锛�
+- **琛ㄥ悕**: `sys_task_additional_fee`
+- **鏂板瀛楁**:
+ - `pid`: 鏃х郴缁熼檮鍔犺垂鐢ㄨ褰旾D锛圥aidMoney_Add.id锛�
+ - `sync_status`: 鍚屾鐘舵�侊紙0鏈悓姝ワ紝1鍚屾涓紝2鍚屾鎴愬姛锛�3鍚屾澶辫触锛�
+ - `sync_time`: 鍚屾鏃堕棿
+
+#### 2. PaidMoneyAdd锛堟棫绯荤粺闄勫姞璐圭敤璁板綍锛�
+- **琛ㄥ悕**: `PaidMoney_Add`
+- **鍏抽敭瀛楁**:
+ - `id`: 涓婚敭
+ - `ToServiceOrdID`: 鏈嶅姟鍗旾D
+ - `ToDispatchOrdID`: 璋冨害鍗旾D
+ - `AddMoneyType`: 闄勫姞璐圭敤绫诲瀷
+ - `AddMoney`: 闄勫姞璐圭敤閲戦
+ - `AddMoneyExplain`: 闄勫姞璐圭敤璇存槑
+ - `AddMoneyTime`: 闄勫姞璐圭敤鏃堕棿
+ - `AddMoneyOAID`: 娣诲姞鐢ㄦ埛鐨凮AID
+
+### 鍏抽敭鍏宠仈
+
+閫氳繃 `SysTaskEmergency` 琛ㄨ幏鍙栦换鍔′笌鏃х郴缁熻鍗曠殑鏄犲皠鍏崇郴锛�
+- `legacy_service_ord_id` 鈫� `ToServiceOrdID`
+- `legacy_dispatch_ord_id` 鈫� `ToDispatchOrdID`
+
+## 鍚屾閫昏緫
+
+### 1. 鏂扮郴缁� 鈫� 鏃х郴缁熷悓姝�
+
+#### 瑙﹀彂鏃舵満
+- 鏂扮郴缁熸坊鍔犻檮鍔犺垂鐢ㄥ悗鑷姩瑙﹀彂
+
+#### 鍚屾娴佺▼
+1. 娣诲姞闄勫姞璐圭敤鍚庯紝璋冪敤 `AdditionalFeeSyncService.syncAdditionalFeeToLegacy(feeId)`
+2. 鏌ヨ鏂扮郴缁熼檮鍔犺垂鐢ㄨ褰曞拰浠诲姟淇℃伅
+3. 鑾峰彇 `ToServiceOrdID` 鍜� `ToDispatchOrdID`
+4. 杞崲璐圭敤绫诲瀷锛堟柊绯荤粺 鈫� 鏃х郴缁燂級
+5. 鏋勫缓 `PaidMoneyAdd` 瀵硅薄骞舵彃鍏ユ棫绯荤粺
+6. 鏇存柊鏂扮郴缁熼檮鍔犺垂鐢ㄨ褰曠殑 `pid`銆乣sync_status`銆乣sync_time`
+
+#### 璐圭敤绫诲瀷鏄犲皠锛堟柊 鈫� 鏃э級
+| 鏂扮郴缁熷瓧鍏稿�� | 鏃х郴缁烝ddMoneyType | 璇存槑 |
+|------------|------------------|------|
+| 1 | 1 | 绛夊緟璐� |
+| 2 | 2 | 鎷呮灦 |
+| 3 | 3 | 灞呭ICU |
+| 4 | 4 | 鍖荤枟璁惧 |
+
+#### 鏁版嵁鏄犲皠
+
+```java
+PaidMoneyAdd.ToServiceOrdID = emergency.legacyServiceOrdId
+PaidMoneyAdd.ToDispatchOrdID = emergency.legacyDispatchOrdId
+PaidMoneyAdd.AddMoneyType = Integer.parseInt(fee.feeType)
+PaidMoneyAdd.AddMoney = fee.totalAmount
+PaidMoneyAdd.AddMoneyExplain = fee.feeName + (澶囨敞)
+PaidMoneyAdd.AddMoneyTime = fee.createdTime
+PaidMoneyAdd.AddMoneyOAID = user.oaUserId
+```
+
+### 2. 鏃х郴缁� 鈫� 鏂扮郴缁熷悓姝�
+
+#### 瑙﹀彂鏃舵満
+- 瀹氭椂浠诲姟锛堟瘡15鍒嗛挓鎵ц涓�娆★級
+- 鎵嬪姩璋冪敤 `AdditionalFeeSyncService.syncAdditionalFeeFromLegacy(paidMoneyAddId)`
+
+#### 鍚屾娴佺▼
+1. 鏌ヨ鏃х郴缁� `PaidMoney_Add` 璁板綍
+2. 妫�鏌ユ槸鍚﹀凡鍚屾锛堥�氳繃 `pid` 瀛楁锛�
+3. 鏍规嵁 `ToServiceOrdID` 鏌ヨ鏂扮郴缁熶换鍔�
+4. 楠岃瘉 `ToDispatchOrdID` 鏄惁鍖归厤
+5. 杞崲璐圭敤绫诲瀷锛堟棫绯荤粺 鈫� 鏂扮郴缁燂級
+6. 鏋勫缓 `SysTaskAdditionalFee` 瀵硅薄骞舵彃鍏ユ柊绯荤粺
+7. 璁剧疆 `pid`銆乣sync_status=2`銆乣sync_time`
+
+#### 璐圭敤绫诲瀷鏄犲皠锛堟棫 鈫� 鏂帮級
+| 鏃х郴缁烝ddMoneyType | 鏂扮郴缁熷瓧鍏稿�� | 璇存槑 |
+|------------------|-----------|------|
+| 1 | 1 | 绛夊緟璐� |
+| 2 | 2 | 鎷呮灦 |
+| 3 | 3 | 灞呭ICU |
+| 4 | 4 | 鍖荤枟璁惧 |
+
+#### 鏁版嵁鏄犲皠
+
+```java
+SysTaskAdditionalFee.taskId = emergency.taskId
+SysTaskAdditionalFee.feeType = String.valueOf(paidMoneyAdd.addMoneyType)
+SysTaskAdditionalFee.feeName = getFeeTypeName(feeType)
+SysTaskAdditionalFee.unitAmount = paidMoneyAdd.addMoney
+SysTaskAdditionalFee.quantity = 1
+SysTaskAdditionalFee.totalAmount = paidMoneyAdd.addMoney
+SysTaskAdditionalFee.remark = paidMoneyAdd.addMoneyExplain
+SysTaskAdditionalFee.pid = paidMoneyAddId
+SysTaskAdditionalFee.syncStatus = 2
+SysTaskAdditionalFee.syncTime = now()
+```
+
+## 鏍稿績浠g爜瀹炵幇
+
+### 鏂囦欢娓呭崟
+
+#### 鏂板鏂囦欢
+1. **瀹炰綋绫�**
+ - `PaidMoneyAdd.java` - 鏃х郴缁熼檮鍔犺垂鐢ㄨ褰曞疄浣�
+
+2. **Mapper鎺ュ彛**
+ - `PaidMoneyAddMapper.java` - 鏃х郴缁熼檮鍔犺垂鐢ㄨ褰昅apper鎺ュ彛
+
+3. **Mapper XML**
+ - `PaidMoneyAddMapper.xml` - 鏃х郴缁熼檮鍔犺垂鐢ㄨ褰昐QL鏄犲皠
+
+4. **Service鎺ュ彛**
+ - `IAdditionalFeeSyncService.java` - 闄勫姞璐圭敤鍚屾鏈嶅姟鎺ュ彛
+
+5. **Service瀹炵幇**
+ - `AdditionalFeeSyncServiceImpl.java` - 闄勫姞璐圭敤鍚屾鏈嶅姟瀹炵幇
+
+6. **鏁版嵁搴撹剼鏈�**
+ - `additional_fee_sync_update.sql` - 鏁版嵁琛ㄦ洿鏂拌剼鏈�
+
+#### 淇敼鏂囦欢
+1. **瀹炰綋绫�**
+ - `SysTaskAdditionalFee.java` - 鏂板 pid銆乻yncStatus銆乻yncTime 瀛楁
+
+2. **Mapper鎺ュ彛**
+ - `SysTaskAdditionalFeeMapper.java` - 鏂板 selectByPid銆乽pdateSyncInfo銆乻electUnsyncedFees 鏂规硶
+
+3. **Mapper XML**
+ - `SysTaskAdditionalFeeMapper.xml` - 鏇存柊鏄犲皠鍜屾柊澧炴煡璇㈡柟娉�
+
+4. **Service瀹炵幇**
+ - `SysTaskPaymentServiceImpl.java` - 娣诲姞闄勫姞璐圭敤鍚庤Е鍙戝悓姝�
+
+5. **瀹氭椂浠诲姟**
+ - `LegacySystemSyncTask.java` - 鏂板闄勫姞璐圭敤鍚屾瀹氭椂浠诲姟鏂规硶
+
+## 鏁版嵁婧愰厤缃�
+
+浣跨敤 `@DataSource` 娉ㄨВ鍒囨崲鏁版嵁婧愶細
+
+```java
+// 璁块棶鏂扮郴缁熸暟鎹簱锛堥粯璁わ級
+public void processNewSystem() {
+ // 榛樿浣跨敤涓绘暟鎹簮
+}
+
+// 璁块棶鏃х郴缁熸暟鎹簱
+@DataSource(DataSourceType.SQLSERVER)
+public void processLegacySystem() {
+ // 浣跨敤SQL Server鏁版嵁婧�
+}
+```
+
+## 鍚屾鐘舵�佺鐞�
+
+### sync_status 鐘舵�佸��
+- **0 - 鏈悓姝�**: 鏂板垱寤虹殑闄勫姞璐圭敤璁板綍锛屽皻鏈悓姝ュ埌鏃х郴缁�
+- **1 - 鍚屾涓�**: 姝e湪鎵ц鍚屾鎿嶄綔
+- **2 - 鍚屾鎴愬姛**: 宸叉垚鍔熷悓姝ュ埌鏃х郴缁�
+- **3 - 鍚屾澶辫触**: 鍚屾杩囩▼涓彂鐢熼敊璇�
+
+### 鐘舵�佹祦杞�
+```
+鏂伴檮鍔犺垂鐢ㄨ褰� 鈫� 0(鏈悓姝�) 鈫� 1(鍚屾涓�) 鈫� 2(鍚屾鎴愬姛) 鎴� 3(鍚屾澶辫触)
+ 鈫�
+ 鍙噸璇�
+```
+
+## 寮傚父澶勭悊
+
+### 鍚屾澶辫触鍦烘櫙
+1. 浠诲姟鏈悓姝ュ埌鏃х郴缁燂紙缂哄皯 ToServiceOrdID/ToDispatchOrdID锛�
+2. 鏃х郴缁熸暟鎹簱杩炴帴澶辫触
+3. 鏁版嵁鎻掑叆鍐茬獊鎴栫害鏉熻繚鍙�
+4. 缃戠粶寮傚父
+
+### 澶勭悊绛栫暐
+1. 鍚屾澶辫触鏃舵爣璁� `sync_status=3`
+2. 璁板綍璇︾粏閿欒鏃ュ織
+3. 涓嶅奖鍝嶄富涓氬姟娴佺▼锛堝紓姝ュ悓姝ワ級
+4. 鍙�氳繃瀹氭椂浠诲姟鎴栨墜鍔ㄨЕ鍙戦噸璇�
+
+## 浣跨敤绀轰緥
+
+### 鎵嬪姩瑙﹀彂鍚屾
+
+```java
+@Autowired
+private IAdditionalFeeSyncService additionalFeeSyncService;
+
+// 鍚屾鍗曟潯闄勫姞璐圭敤鍒版棫绯荤粺
+boolean success = additionalFeeSyncService.syncAdditionalFeeToLegacy(feeId);
+
+// 浠庢棫绯荤粺鍚屾闄勫姞璐圭敤
+boolean success = additionalFeeSyncService.syncAdditionalFeeFromLegacy(paidMoneyAddId);
+
+// 鎵归噺鍚屾
+int count = additionalFeeSyncService.batchSyncAdditionalFeeToLegacy();
+int count = additionalFeeSyncService.batchSyncAdditionalFeeFromLegacy(24); // 鍚屾24灏忔椂鍐呯殑璁板綍
+```
+
+### 閰嶇疆瀹氭椂浠诲姟
+
+#### 鏂扮郴缁� 鈫� 鏃х郴缁熷悓姝�
+```
+浠诲姟鍚嶇О: 闄勫姞璐圭敤鍚屾
+浠诲姟缁勫悕: DEFAULT
+璋冪敤鐩爣: legacySystemSyncTask.syncAdditionalFeeToLegacy()
+cron琛ㄨ揪寮�: 0 0/10 * * * ? (姣�10鍒嗛挓鎵ц涓�娆�)
+```
+
+#### 鏃х郴缁� 鈫� 鏂扮郴缁熷悓姝�
+```
+浠诲姟鍚嶇О: 闄勫姞璐圭敤鍙嶅悜鍚屾
+浠诲姟缁勫悕: DEFAULT
+璋冪敤鐩爣: legacySystemSyncTask.syncAdditionalFeeFromLegacy()
+cron琛ㄨ揪寮�: 0 0/15 * * * ? (姣�15鍒嗛挓鎵ц涓�娆�)
+```
+
+## 鏁版嵁搴撴洿鏂�
+
+鎵ц SQL 鑴氭湰锛�
+```sql
+source sql/additional_fee_sync_update.sql
+```
+
+## 娉ㄦ剰浜嬮」
+
+1. **鍓嶇疆鏉′欢**: 浠诲姟蹇呴』宸茬粡鍚屾鍒版棫绯荤粺锛堟湁 ServiceOrdID 鍜� DispatchOrdID锛�
+2. **璐圭敤绫诲瀷**: 浣跨敤缁熶竴鐨勫瓧鍏稿�硷紙1-绛夊緟璐�, 2-鎷呮灦, 3-灞呭ICU, 4-鍖荤枟璁惧锛�
+3. **鍚屾闂撮殧**: 鏂扮郴缁熲啋鏃х郴缁熺珛鍗冲悓姝ワ紝鏃х郴缁熲啋鏂扮郴缁熷畾鏃跺悓姝�
+4. **閿欒澶勭悊**: 鍚屾澶辫触涓嶅奖鍝嶄富涓氬姟锛屽彲浠ラ噸璇�
+5. **鎬ц兘浼樺寲**: 鎵归噺鍚屾闄愬埗姣忔100鏉¤褰曪紝姣忔潯璁板綍闂撮殧1绉�
+
+## 楠岃瘉娴嬭瘯
+
+### 娴嬭瘯姝ラ
+
+1. **鏂板闄勫姞璐圭敤锛堟柊绯荤粺 鈫� 鏃х郴缁燂級**
+ - 鍦ˋPP涓负杞繍浠诲姟娣诲姞闄勫姞璐圭敤
+ - 妫�鏌ユ柊绯荤粺 `sys_task_additional_fee` 琛ㄧ殑 `sync_status` 瀛楁
+ - 妫�鏌ユ棫绯荤粺 `PaidMoney_Add` 琛ㄦ槸鍚︽湁瀵瑰簲璁板綍
+
+2. **鏃х郴缁熸柊澧為檮鍔犺垂鐢紙鏃х郴缁� 鈫� 鏂扮郴缁燂級**
+ - 鍦ㄦ棫绯荤粺涓坊鍔犻檮鍔犺垂鐢ㄨ褰�
+ - 杩愯瀹氭椂浠诲姟鎴栨墜鍔ㄨ皟鐢ㄥ悓姝ユ柟娉�
+ - 妫�鏌ユ柊绯荤粺 `sys_task_additional_fee` 琛ㄦ槸鍚︽湁瀵瑰簲璁板綍
+
+### 鏌ヨ楠岃瘉SQL
+
+```sql
+-- 鏌ョ湅鏂扮郴缁熼檮鍔犺垂鐢ㄥ強鍚屾鐘舵��
+SELECT id, task_id, fee_type, fee_name, total_amount, sync_status, pid
+FROM sys_task_additional_fee
+WHERE task_id = ?;
+
+-- 鏌ョ湅鏃х郴缁熼檮鍔犺垂鐢紙鍦⊿QL Server涓墽琛岋級
+SELECT id, ToServiceOrdID, ToDispatchOrdID, AddMoneyType, AddMoney, AddMoneyExplain
+FROM PaidMoney_Add
+WHERE ToServiceOrdID = ? AND ToDispatchOrdID = ?;
+
+-- 鏌ョ湅鏈悓姝ョ殑闄勫姞璐圭敤
+SELECT *
+FROM sys_task_additional_fee
+WHERE (sync_status = 0 OR sync_status = 3 OR sync_status IS NULL);
+```
+
+## 鎶�鏈鐐�
+
+1. **鍙屽悜鍚屾**: 鏀寔鏂版棫绯荤粺涔嬮棿鐨勫弻鍚戞暟鎹悓姝�
+2. **骞傜瓑鎬�**: 閫氳繃 pid 瀛楁閬垮厤閲嶅鍚屾
+3. **浜嬪姟鎬�**: 浣跨敤 @Transactional 纭繚鏁版嵁涓�鑷存��
+4. **寮傛澶勭悊**: 鍚屾涓嶉樆濉炰富涓氬姟娴佺▼
+5. **閿欒閲嶈瘯**: 鏀寔澶辫触璁板綍鐨勯噸鏂板悓姝�
diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index 624a352..a77a0d9 100644
--- a/ruoyi-admin/pom.xml
+++ b/ruoyi-admin/pom.xml
@@ -63,6 +63,14 @@
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-generator</artifactId>
</dependency>
+
+ <!-- 鏀粯妯″潡 -->
+ <dependency>
+ <groupId>com.ruoyi</groupId>
+ <artifactId>dryad-payment</artifactId>
+ <version>1.0.0</version>
+ </dependency>
+
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
index cd4693f..e17fb3b 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
@@ -3,6 +3,7 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
/**
* 鍚姩绋嬪簭
@@ -10,6 +11,7 @@
* @author ruoyi
*/
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
+@ComponentScan(basePackages = {"com.ruoyi", "com.ruoyi.payment"})
public class RuoYiApplication
{
public static void main(String[] args)
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/PaymentCallbackController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/PaymentCallbackController.java
new file mode 100644
index 0000000..abb8ba6
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/PaymentCallbackController.java
@@ -0,0 +1,75 @@
+package com.ruoyi.web.controller.common;
+
+import java.util.Map;
+
+import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.system.service.ISysTaskPaymentService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 鏀粯鍥炶皟Controller
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+@RestController
+@RequestMapping("/payment/callback")
+public class PaymentCallbackController extends BaseController {
+
+ private static final Logger log = LoggerFactory.getLogger(PaymentCallbackController.class);
+
+ @Autowired
+ private ISysTaskPaymentService taskPaymentService;
+
+ /**
+ * 寰俊鏀粯鍥炶皟
+ */
+ @Anonymous
+ @PostMapping("/wechat")
+ public AjaxResult wechatCallback(@RequestBody Map<String, Object> params) {
+ log.info("鏀跺埌寰俊鏀粯鍥炶皟锛歿}", params);
+
+ // TODO: 瀹為檯椤圭洰涓渶瑕侀獙璇佺鍚�
+ String outTradeNo = (String) params.get("outTradeNo");
+ if (StringUtils.isEmpty(outTradeNo)) {
+ outTradeNo = (String) params.get("bizOrderId");
+ }
+ String tradeNo = (String) params.get("tradeNo");
+ if (StringUtils.isEmpty(tradeNo)) {
+ tradeNo = (String) params.get("channelTradeNo");
+ }
+
+ boolean success = taskPaymentService.handlePaymentCallback(outTradeNo, tradeNo, "WECHAT");
+
+ return success ? success() : error("澶勭悊澶辫触");
+ }
+
+ /**
+ * 鏀粯瀹濇敮浠樺洖璋�
+ */
+ @Anonymous
+ @PostMapping("/alipay")
+ public AjaxResult alipayCallback(@RequestBody Map<String, Object> params) {
+ log.info("鏀跺埌鏀粯瀹濇敮浠樺洖璋冿細{}", params);
+
+ // TODO: 瀹為檯椤圭洰涓渶瑕侀獙璇佺鍚�
+ String outTradeNo = (String) params.get("outTradeNo");
+ if (StringUtils.isEmpty(outTradeNo)) {
+ outTradeNo = (String) params.get("bizOrderId");
+ }
+ String tradeNo = (String) params.get("tradeNo");
+ if (StringUtils.isEmpty(tradeNo)) {
+ tradeNo = (String) params.get("channelTradeNo");
+ }
+
+ boolean success = taskPaymentService.handlePaymentCallback(outTradeNo, tradeNo, "ALIPAY");
+
+ return success ? success() : error("澶勭悊澶辫触");
+ }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskPaymentController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskPaymentController.java
new file mode 100644
index 0000000..24baba1
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskPaymentController.java
@@ -0,0 +1,107 @@
+package com.ruoyi.web.controller.task;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.vo.TaskPaymentInfoVO;
+import com.ruoyi.system.domain.vo.TaskPaymentCreateVO;
+import com.ruoyi.system.domain.vo.TaskPaymentResultVO;
+import com.ruoyi.system.service.ISysTaskPaymentService;
+
+/**
+ * 浠诲姟鏀粯Controller
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+@RestController
+@RequestMapping("/task/payment")
+public class SysTaskPaymentController extends BaseController {
+
+ @Autowired
+ private ISysTaskPaymentService taskPaymentService;
+
+ /**
+ * 鑾峰彇浠诲姟鏀粯淇℃伅
+ */
+ @GetMapping("/info")
+ public AjaxResult getPaymentInfo(@RequestParam Long taskId) {
+ TaskPaymentInfoVO paymentInfo = taskPaymentService.getPaymentInfo(taskId);
+ return success(paymentInfo);
+ }
+
+ /**
+ * 鏂板闄勫姞璐圭敤
+ */
+ @Log(title = "鏂板闄勫姞璐圭敤", businessType = BusinessType.INSERT)
+ @PostMapping("/additional-fee/add")
+ public AjaxResult addAdditionalFee(@RequestBody Map<String, Object> params) {
+ Long taskId = Long.valueOf(params.get("taskId").toString());
+ String feeType = (String) params.get("feeType");
+ String feeName = (String) params.get("feeName");
+ BigDecimal unitAmount = new BigDecimal(params.get("unitAmount").toString());
+ Integer quantity = Integer.valueOf(params.get("quantity").toString());
+ String remark = params.get("remark") != null ? params.get("remark").toString() : null;
+
+ BigDecimal additionalAmount = taskPaymentService.addAdditionalFee(
+ taskId, feeType, feeName, unitAmount, quantity, remark
+ );
+
+ // 閲嶆柊鑾峰彇鏀粯淇℃伅
+ TaskPaymentInfoVO paymentInfo = taskPaymentService.getPaymentInfo(taskId);
+
+ Map<String, Object> result = new HashMap<>();
+ result.put("additionalAmount", additionalAmount);
+ result.put("totalAmount", paymentInfo.getTotalAmount());
+
+ return success(result);
+ }
+
+ /**
+ * 鍒犻櫎闄勫姞璐圭敤
+ */
+ @Log(title = "鍒犻櫎闄勫姞璐圭敤", businessType = BusinessType.DELETE)
+ @PostMapping("/additional-fee/remove")
+ public AjaxResult removeAdditionalFee(@RequestBody Map<String, Object> params) {
+ Long taskId = Long.valueOf(params.get("taskId").toString());
+ Long feeId = Long.valueOf(params.get("feeId").toString());
+
+ BigDecimal additionalAmount = taskPaymentService.removeAdditionalFee(taskId, feeId);
+
+ // 閲嶆柊鑾峰彇鏀粯淇℃伅
+ TaskPaymentInfoVO paymentInfo = taskPaymentService.getPaymentInfo(taskId);
+
+ Map<String, Object> result = new HashMap<>();
+ result.put("additionalAmount", additionalAmount);
+ result.put("totalAmount", paymentInfo.getTotalAmount());
+
+ return success(result);
+ }
+
+ /**
+ * 鍒涘缓鏀粯
+ */
+ @Log(title = "鍒涘缓鏀粯", businessType = BusinessType.INSERT)
+ @PostMapping("/create")
+ public AjaxResult createPayment(@RequestBody TaskPaymentCreateVO createVO) {
+ TaskPaymentResultVO result = taskPaymentService.createPayment(createVO);
+ return success(result);
+ }
+
+ /**
+ * 鏌ヨ鏀粯鐘舵��
+ */
+ @GetMapping("/status")
+ public AjaxResult getPaymentStatus(@RequestParam Long taskId,
+ @RequestParam(required = false) Long paymentId) {
+ TaskPaymentResultVO result = taskPaymentService.getPaymentStatus(taskId, paymentId);
+ return success(result);
+ }
+}
diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml
index 73d26f3..e4c6e54 100644
--- a/ruoyi-admin/src/main/resources/application-dev.yml
+++ b/ruoyi-admin/src/main/resources/application-dev.yml
@@ -6,7 +6,7 @@
druid:
# 涓诲簱鏁版嵁婧�
master:
- url: jdbc:mysql://120.25.98.119:3307/ruoyi?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+ url: jdbc:mysql://127.0.0.1:3306/966120?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: abcd1234
# 浠庡簱鏁版嵁婧�
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index 9db576a..ce30935 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -170,3 +170,58 @@
tianditu:
map:
tk: 4d1d0b3a4a03b9c5099c0e25ab1b23f3 # 璇锋浛鎹负鎮ㄧ殑澶╁湴鍥続PI Key
+
+# 鍦板浘鏈嶅姟閰嶇疆
+# provider: 鍦板浘鏈嶅姟鎻愪緵鍟嗭紝鍙�夊�硷細baidu锛堢櫨搴﹀湴鍥撅級銆乼ianditu锛堝ぉ鍦板浘锛�
+map:
+ service:
+ provider: baidu # 榛樿浣跨敤鐧惧害鍦板浘
+
+# 鏀粯閰嶇疆
+payment:
+ # 鏄惁鍚敤鏀粯妯″潡锛堜綔涓虹粍浠堕泦鎴愭椂鍙�氳繃姝ゅ紑鍏虫帶鍒讹級
+ enabled: true
+
+ # 寰俊鏀粯閰嶇疆
+ wechat:
+ appId: wx70f6a7346ee842c0
+ mchId: 1573728151
+ appName: 姘戣埅鍖荤枟蹇嚎
+ mchKey: Xz0ClPK3f5sCeT6SGTaha1vpVmyUFcbp
+ notifyUrl: https://dsp.966120.com/api/pay/notify/wechat
+ signType: MD5
+ checkSign: false
+
+ # 鏀粯瀹濋厤缃�
+ alipay:
+ appId: 2018060160299486
+ privateKey: MIIEpgIBAAKCAQEArt273bWTEPXPjCsUYwFx7CNjhcQlm1NtbNjfeIsZ2g9sbFCQP9qpyufp6zkBv6eq+6WEztkC1KwSsuDjP5LvgY/1pmGFlr8r7cjeZI4bTeIe9jG5UaHolnzbdXUlSoInzgWRvbYXxuQZciwVpokwviW27YK9wPIzz9OTiRquL8b3YWPZLO7xK0gBMa2KfFfUXxCB8gHJtidQ+FjjYXb2WpnScKLJdKfWcGWFnyGiZOknyFR9kI8cm0cYPNHtecQId0bQ1ee7YDLD8dBPd2Pd/JBC4Wn6HuOvZOLqZvIpIj+8q0zGXjUUns6MsjNL3MUKuhKy6hQGwP5sGrPcVcwqiwIDAQABAoIBAQCTeW9iSSsRx61VUlOsN+DDPQlHHCh3OcH0ZWb6e52+2Pkg1EUDhT9jX3lZJsfBwf8iofJCnKSVhdVzRNSCnkIdq7KJsn9+phW/QYPFnE+MvKJOEZtwLDNDD2PqSHS9xM0bJHlIXNTqqR6IuoM740HXa2k+H+A2ZE2r/YzUuUqkASwAYPKYzWa1wivg4CZrvoPZ5bXvYOHoV0jZEtyUQB9dHuCz+bghbR+28vGkYwEGInLsOCe6Gl5D61F0l2qAXRQky3P0jXxIPXPFmhBYutAAUufLpryruQgL3MDDm5dhBJpwp89qwFDjc8fWVS/FFYJ0KDQOpAxAI800YHgaJ4VBAoGBAOdCcoYS49Q9Iula7gFGVXeto7QSUsP0CnQZM/tsAU6TiX3XG5pxBYoVmYSIQPylgagRTBZHD5t/LGa+I+KYMHSVAH/kndPdgTO7EiwfUCzi1DmGZVWs7XJ2zRfTmYRVdsElPy3Z8Pm802jd7mhffw/5p6y9pzNJqOjmGOUbYGrrAoGBAMGS1CGHkK4nD2BsIWJoKW2Lxph3Bq5hN1iaE8ZjOAvFT9drNqfwRa6BVGyfYEXPZTvUT0FUNMdukcTCM6O3FRU8EABJIcVue2QA+BvkfwPEU3JxMA8DrWHO7IcgtG9wjbxretDsf+SZmkQgK0ZUPod5ZZSycFxM3/GiXDQjGhbhAoGBAL/Y/+j6AscvcKbmKEwmbQC7q/LWwJKPAZ0Oy3DoSK1G9+jNarjUyiOjh5fK8R6mrskekGBq0yfMeKlDU8HHP2t3sNJodgYs2+JubsTrtTeHdUfDlo1cyB8NL1d00wZVYA8bNy5yftavLzLv6bfsgRxfoBpNu0dw9A9B06U88N/BAoGBAKCN/nEJFlG8iB570Xzj1GjOJJzVLK96ZwOQWJKWPShWMhEFFkJZIhLJppKp5ppAmUD0qgAPre80oKdIRLin5E7GkKcMAXzWVHXv79qCvW8MagJkK25oqGiVzs2NrNs5yfXcV/PuFW4wkSmsXPhqa6rGYCDjmBqWkLDE8CE2dC9BAoGBAJ2+QfAAvB27mr48+vY4HxZyPRrCBA1YkokraWQ0IlfD0MKsFw0Af4Iu3oy7V6NlE2GwL/AcObyHeGZt7DLbViAQOgmb9BpUrjUZ4bXSBuGPfRe11HCu6j6W67qa76TAoy3A0Dfm0OE/m9r4H99NaLzeBm1KluySKkfYXoqyueQw
+ alipayPublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl8BjXYaudmGT+sEos0AXEUrKl6+wyw6++hBoJDdY7P+P7poa34GN4YSkkavTA/HplmRM1wTLcY+NqhxhLpNrcgy08AbC/GGcLM2wxQGFa+L+DQLz34uBAShXDK8yN6O49UdbbJ2RRaJSAb+nW4ZVCPMGtMu4S3lXTymQgizM1IYo9L92U5QPRzZRSP8+AmQPzwofRqEgvkO02s66xU2G5AAdkVg5BQm34eM0Io2CmcWF9jSoWQTJdyd7tw3oec9NqD7x3CfcsN3NAJOQLz4+bWWqDWelyviyAr2reeH6AuBVjaWwAvAJx3yuLevKMXTzPC95Ja7w4XYSB9Fg2+aKmwIDAQAB
+ serverUrl: https://openapi.alipay.com/gateway.do
+ notifyUrl: https://dsp.966120.com/api/pay/notify/alipay
+ signType: RSA2
+ checkSign: false
+ # 鏀粯鏂瑰紡: OFFICIAL(瀹樻柟鏀粯瀹�) 鎴� THIRD_PARTY(绗笁鏂规敮浠樺疂)
+ paymentMethod: THIRD_PARTY
+ # 绗笁鏂规敮浠橀厤缃�
+ thirdParty:
+ enabled: true
+ url: https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php
+ defaultNotifyUrl: https://dsp.966120.com.cn/alipay/pay_notify
+ timeout: 30000
+
+ # 涓氬姟鍥炶皟閰嶇疆
+ business:
+ callbackSignSecret: your-hmac-secret-key-here
+ callbackRetryMaxCount: 10
+ callbackRetryIntervals: 0,1,5,15,60
+
+ # 浜岀淮鐮侀厤缃�
+ qrcode:
+ size: 300
+ format: PNG
+
+ # 瀵硅处閰嶇疆
+ reconciliation:
+ enabled: true
+ cron: "0 0 2 * * ?"
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/LegacySystemConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/LegacySystemConfig.java
index 02ef3e2..55b9de2 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/config/LegacySystemConfig.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/LegacySystemConfig.java
@@ -21,6 +21,9 @@
/** 璋冨害鍗曞垱寤烘帴鍙h矾寰� */
private String dispatchCreatePath = "/admin_save_24.gds";
+ /** 璋冨害鍗曟洿鏂版帴鍙h矾寰勶紙杞﹁締/浜哄憳鍙樻洿锛� */
+ private String dispatchUpdatePath = "/admin_save_25.asp";
+
/** 浠诲姟鐘舵�佹煡璇㈡帴鍙h矾寰勶紙宸插純鐢紝鐩存帴鏌ヨSQL Server鏁版嵁搴擄級 */
@Deprecated
private String statusQueryPath = "/task_status_query.asp";
@@ -80,6 +83,14 @@
this.dispatchCreatePath = dispatchCreatePath;
}
+ public String getDispatchUpdatePath() {
+ return dispatchUpdatePath;
+ }
+
+ public void setDispatchUpdatePath(String dispatchUpdatePath) {
+ this.dispatchUpdatePath = dispatchUpdatePath;
+ }
+
public int getConnectTimeout() {
return connectTimeout;
}
@@ -126,6 +137,13 @@
return baseUrl + dispatchCreatePath;
}
+ /**
+ * 鑾峰彇瀹屾暣鐨勮皟搴﹀崟鏇存柊URL锛堣溅杈�/浜哄憳鍙樻洿锛�
+ */
+ public String getDispatchUpdateUrl() {
+ return baseUrl + dispatchUpdatePath;
+ }
+
public String getStatusQueryPath() {
return statusQueryPath;
}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/MapServiceConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/MapServiceConfig.java
new file mode 100644
index 0000000..fa094a8
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/MapServiceConfig.java
@@ -0,0 +1,28 @@
+package com.ruoyi.common.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 鍦板浘鏈嶅姟閰嶇疆绫�
+ * 鐢ㄤ簬閰嶇疆浣跨敤鍝釜鍦板浘鏈嶅姟鎻愪緵鍟�
+ */
+@Configuration
+@ConfigurationProperties(prefix = "map.service")
+public class MapServiceConfig {
+
+ /**
+ * 鍦板浘鏈嶅姟鎻愪緵鍟嗙被鍨�
+ * 鍙�夊��: baidu, tianditu
+ * 榛樿: baidu
+ */
+ private String provider = "baidu";
+
+ public String getProvider() {
+ return provider;
+ }
+
+ public void setProvider(String provider) {
+ this.provider = provider;
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java
index 272342b..fcb6890 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java
@@ -61,6 +61,15 @@
/** 璋冨害鍗曠紪鐮�(瀵瑰簲鏃х郴缁烵rderClass vType=2) */
private String dispatchOrderClass;
+ /** 榛樿鍑鸿溅鍦板潃 */
+ private String departureAddress;
+
+ /** 鍑鸿溅鍦板潃缁忓害 */
+ private java.math.BigDecimal departureLongitude;
+
+ /** 鍑鸿溅鍦板潃绾害 */
+ private java.math.BigDecimal departureLatitude;
+
/** 瀛愰儴闂� */
private List<SysDept> children = new ArrayList<SysDept>();
@@ -220,6 +229,36 @@
this.dispatchOrderClass = dispatchOrderClass;
}
+ public String getDepartureAddress()
+ {
+ return departureAddress;
+ }
+
+ public void setDepartureAddress(String departureAddress)
+ {
+ this.departureAddress = departureAddress;
+ }
+
+ public java.math.BigDecimal getDepartureLongitude()
+ {
+ return departureLongitude;
+ }
+
+ public void setDepartureLongitude(java.math.BigDecimal departureLongitude)
+ {
+ this.departureLongitude = departureLongitude;
+ }
+
+ public java.math.BigDecimal getDepartureLatitude()
+ {
+ return departureLatitude;
+ }
+
+ public void setDepartureLatitude(java.math.BigDecimal departureLatitude)
+ {
+ this.departureLatitude = departureLatitude;
+ }
+
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MybatisPlusConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MybatisPlusConfig.java
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MybatisPlusConfig.java
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/DepartmentSyncTask.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/DepartmentSyncTask.java
index b3bdca1..ddd0d64 100644
--- a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/DepartmentSyncTask.java
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/DepartmentSyncTask.java
@@ -12,6 +12,7 @@
import com.ruoyi.system.service.IDepartmentSyncService;
import java.util.List;
+import java.util.Map;
/**
* 閮ㄩ棬鍚屾瀹氭椂浠诲姟
@@ -39,13 +40,16 @@
}
@Autowired
private IOrderClassDataService orderClassDataService;
-private List<OrderClassDTO> getServiceOrdCode() {
- return orderClassDataService.getServiceOrderClass();
-}
-private List<OrderClassDTO> getDispatchOrdCode() {
- return orderClassDataService.getDispatchOrderClass();
-}
+ private List<OrderClassDTO> getServiceOrdCode() {
+ return orderClassDataService.getServiceOrderClass();
+ }
+ private List<OrderClassDTO> getDispatchOrdCode() {
+ return orderClassDataService.getDispatchOrderClass();
+ }
+ private List<Map<String, Object>> getDepartAddress() {
+ return departmentSyncDataService.getAddressList();
+ }
/**
* 鍚屾鍒嗗叕鍙稿拰閮ㄩ棬鏁版嵁
*
@@ -59,10 +63,12 @@
try
{
log.info("==========寮�濮嬫墽琛岄儴闂ㄥ悓姝ュ畾鏃朵换鍔�==========");
-
- AjaxResult result = departmentSyncService.syncBranchDepartments(this.getDepartment(), this.getServiceOrdCode(),this.getDispatchOrdCode());
- departmentSyncService.syncTransportDepartments(this.getTransportDept());
+ List<Map<String, Object>> addressList = this.getDepartAddress();
+ AjaxResult result = departmentSyncService.syncBranchDepartments(this.getDepartment(), this.getServiceOrdCode(),this.getDispatchOrdCode(),addressList);
+
+ departmentSyncService.syncTransportDepartments(this.getTransportDept(),addressList);
+ departmentSyncService.syncDeptAddress(addressList);
if (result.get("code").equals(200))
{
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/LegacySystemSyncTask.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/LegacySystemSyncTask.java
index 6f28280..af6c684 100644
--- a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/LegacySystemSyncTask.java
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/LegacySystemSyncTask.java
@@ -1,6 +1,8 @@
package com.ruoyi.quartz.task;
import com.ruoyi.system.service.ITaskAttachmentSyncService;
+import com.ruoyi.system.service.IPaymentSyncService;
+import com.ruoyi.system.service.IAdditionalFeeSyncService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -22,6 +24,8 @@
* 3. TaskStatusSyncService - 浠诲姟鐘舵�佸悓姝ワ紙浠庢棫绯荤粺鍒版柊绯荤粺锛�
* 4. TaskStatusPushService - 浠诲姟鐘舵�佹帹閫侊紙浠庢柊绯荤粺鍒版棫绯荤粺锛�
* 5. TaskAttachmentSyncService - 浠诲姟闄勪欢鍚屾锛堜粠鏂扮郴缁熷埌鏃х郴缁燂級
+ * 6. PaymentSyncService - 鏀粯淇℃伅鍚屾锛堝弻鍚戝悓姝ワ級
+ * 7. AdditionalFeeSyncService - 闄勫姞璐圭敤鍚屾锛堝弻鍚戝悓姝ワ級
*/
@Component("legacySystemSyncTask")
public class LegacySystemSyncTask {
@@ -39,6 +43,12 @@
@Autowired
private ITaskAttachmentSyncService taskAttachmentSyncService;
+
+ @Autowired
+ private IPaymentSyncService paymentSyncService;
+
+ @Autowired
+ private IAdditionalFeeSyncService additionalFeeSyncService;
@@ -166,4 +176,105 @@
log.error("浠诲姟闄勪欢鍚屾寮傚父", e);
}
}
+
+ /**
+ * 鎵归噺鍚屾鏀粯淇℃伅鍒版棫绯荤粺PaidMoney琛�
+ *
+ * 浣跨敤绀轰緥:
+ * 鍦ㄧ郴缁熺鐞� -> 瀹氭椂浠诲姟涓坊鍔�:
+ * 浠诲姟鍚嶇О: 鏀粯淇℃伅鍚屾
+ * 浠诲姟缁勫悕: DEFAULT
+ * 璋冪敤鐩爣瀛楃涓�: legacySystemSyncTask.syncPaymentToLegacy()
+ * cron琛ㄨ揪寮�: 0 0/10 * * * ? (姣�10鍒嗛挓鎵ц涓�娆�)
+ */
+ public void syncPaymentToLegacy() {
+ log.info("寮�濮嬫墽琛屾敮浠樹俊鎭悓姝ュ畾鏃朵换鍔★紙鏂扮郴缁� -> 鏃х郴缁燂級");
+ try {
+ int successCount = paymentSyncService.batchSyncPaymentToLegacy();
+ log.info("鏀粯淇℃伅鍚屾瀹屾垚锛屾垚鍔熷悓姝�: {} 鏉¤褰�", successCount);
+ } catch (Exception e) {
+ log.error("鏀粯淇℃伅鍚屾寮傚父", e);
+ }
+ }
+
+ /**
+ * 鎵归噺鍚屾鏀粯淇℃伅浠庢棫绯荤粺鍒版柊绯荤粺
+ *
+ * 浣跨敤绀轰緥:
+ * 鍦ㄧ郴缁熺鐞� -> 瀹氭椂浠诲姟涓坊鍔�:
+ * 浠诲姟鍚嶇О: 鏀粯淇℃伅鍙嶅悜鍚屾
+ * 浠诲姟缁勫悕: DEFAULT
+ * 璋冪敤鐩爣瀛楃涓�: legacySystemSyncTask.syncPaymentFromLegacy()
+ * cron琛ㄨ揪寮�: 0 0/15 * * * ? (姣�15鍒嗛挓鎵ц涓�娆�)
+ */
+ public void syncPaymentFromLegacy() {
+ log.info("寮�濮嬫墽琛屾敮浠樹俊鎭悓姝ュ畾鏃朵换鍔★紙鏃х郴缁� -> 鏂扮郴缁燂級");
+ try {
+ int successCount = paymentSyncService.batchSyncPaymentFromLegacy();
+ log.info("鏀粯淇℃伅鍚屾瀹屾垚锛屾垚鍔熷悓姝�: {} 鏉¤褰�", successCount);
+ } catch (Exception e) {
+ log.error("鏀粯淇℃伅鍚屾寮傚父", e);
+ }
+ }
+
+ /**
+ * 鎵归噺鍚屾闄勫姞璐圭敤鍒版棫绯荤粺PaidMoney_Add琛�
+ *
+ * 浣跨敤绀轰緥:
+ * 鍦ㄧ郴缁熺鐞� -> 瀹氭椂浠诲姟涓坊鍔�:
+ * 浠诲姟鍚嶇О: 闄勫姞璐圭敤鍚屾
+ * 浠诲姟缁勫悕: DEFAULT
+ * 璋冪敤鐩爣瀛楃涓�: legacySystemSyncTask.syncAdditionalFeeToLegacy()
+ * cron琛ㄨ揪寮�: 0 0/10 * * * ? (姣�10鍒嗛挓鎵ц涓�娆�)
+ */
+ public void syncAdditionalFeeToLegacy() {
+ log.info("寮�濮嬫墽琛岄檮鍔犺垂鐢ㄥ悓姝ュ畾鏃朵换鍔★紙鏂扮郴缁� -> 鏃х郴缁燂級");
+ try {
+ int successCount = additionalFeeSyncService.batchSyncAdditionalFeeToLegacy();
+ log.info("闄勫姞璐圭敤鍚屾瀹屾垚锛屾垚鍔熷悓姝�: {} 鏉¤褰�", successCount);
+ } catch (Exception e) {
+ log.error("闄勫姞璐圭敤鍚屾寮傚父", e);
+ }
+ }
+
+ /**
+ * 鎵归噺鍚屾闄勫姞璐圭敤浠庢棫绯荤粺鍒版柊绯荤粺
+ *
+ * 浣跨敤绀轰緥锛�
+ * 鍦ㄧ郴缁熺鐞� -> 瀹氭椂浠诲姟涓坊鍔狅細
+ * 浠诲姟鍚嶇О锛� 闄勫姞璐圭敤鍙嶅悜鍚屾
+ * 浠诲姟缁勫悕锛� DEFAULT
+ * 璋冪敤鐩爣瀛楃涓诧細 legacySystemSyncTask.syncAdditionalFeeFromLegacy()
+ * cron琛ㄨ揪寮忥細 0 0/15 * * * ? (姣�15鍒嗛挓鎵ц涓�娆�)
+ */
+ public void syncAdditionalFeeFromLegacy() {
+ log.info("寮�濮嬫墽琛岄檮鍔犺垂鐢ㄥ悓姝ュ畾鏃朵换鍔★紙鏃х郴缁� -> 鏂扮郴缁燂級");
+ try {
+ // 鍚屾鏈�杩�24灏忔椂鍐呯殑璁板綍
+ int successCount = additionalFeeSyncService.batchSyncAdditionalFeeFromLegacy(24);
+ log.info("闄勫姞璐圭敤鍚屾瀹屾垚锛屾垚鍔熷悓姝�: {} 鏉¤褰�", successCount);
+ } catch (Exception e) {
+ log.error("闄勫姞璐圭敤鍚屾寮傚父", e);
+ }
+ }
+
+ /**
+ * 鎵归噺閲嶆柊鍚屾杞﹁締鍜屼汉鍛樺彉鏇寸殑浠诲姟
+ *
+ * 浣跨敤绀轰緥锛�
+ * 鍦ㄧ郴缁熺鐞� -> 瀹氭椂浠诲姟涓坊鍔狅細
+ * 浠诲姟鍚嶇О锛� 閲嶆柊鍚屾杞﹁締浜哄憳鍙樻洿
+ * 浠诲姟缁勫悕锛� DEFAULT
+ * 璋冪敤鐩爣瀛楃涓诧細 legacySystemSyncTask.resyncVehicleAndPersonnel()
+ * cron琛ㄨ揪寮忥細 0 0/5 * * * ? (姣�5鍒嗛挓鎵ц涓�娆�)
+ */
+ public void resyncVehicleAndPersonnel() {
+ log.info("寮�濮嬫墽琛岄噸鏂板悓姝ヨ溅杈嗗拰浜哄憳鍙樻洿鐨勪换鍔″畾鏃朵换鍔�");
+ try {
+ int successCount = legacySystemSyncService.batchResyncPendingDispatchOrders();
+ log.info("閲嶆柊鍚屾杞﹁締鍜屼汉鍛樺彉鏇村畬鎴愶紝鎴愬姛鍚屾: {} 涓换鍔�", successCount);
+ } catch (Exception e) {
+ log.error("閲嶆柊鍚屾杞﹁締鍜屼汉鍛樺彉鏇村紓甯�", e);
+ }
+ }
}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/OaSyncTask.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/OaSyncTask.java
index 775f837..97d811a 100644
--- a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/OaSyncTask.java
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/OaSyncTask.java
@@ -11,6 +11,7 @@
import com.ruoyi.common.core.domain.AjaxResult;
import java.util.List;
+import java.util.Map;
/**
* OA鏁版嵁鍚屾瀹氭椂浠诲姟锛堢粍鍚堜换鍔★級
@@ -53,6 +54,10 @@
private List<OrderClassDTO> getDispatchOrdCode() {
return orderClassDataService.getDispatchOrderClass();
}
+
+ private List<Map<String,Object>> getAddressList() {
+ return departmentSyncDataService.getAddressList();
+ }
/**
* 鍚屾OA鏁版嵁锛堥儴闂�+鐢ㄦ埛锛�
* 鎸夐『搴忔墽琛岋細1. 閮ㄩ棬鍚屾 2. 鐢ㄦ埛鍚屾
@@ -70,7 +75,7 @@
// 绗竴姝ワ細鍚屾閮ㄩ棬
log.info("銆愭楠�1/2銆戝紑濮嬪悓姝ラ儴闂ㄦ暟鎹�...");
- AjaxResult deptResult = departmentSyncService.syncBranchDepartments(this.getDept(),this.getServiceOrdCode(),this.getDispatchOrdCode());
+ AjaxResult deptResult = departmentSyncService.syncBranchDepartments(this.getDept(),this.getServiceOrdCode(),this.getDispatchOrdCode(),this.getAddressList());
if (deptResult.get("code").equals(200))
{
@@ -125,7 +130,7 @@
try
{
log.info("==========寮�濮嬫墽琛岄儴闂ㄥ悓姝ヤ换鍔�==========");
- AjaxResult result = departmentSyncService.syncBranchDepartments(this.getDept(),this.getServiceOrdCode(),this.getDispatchOrdCode());
+ AjaxResult result = departmentSyncService.syncBranchDepartments(this.getDept(),this.getServiceOrdCode(),this.getDispatchOrdCode(),this.getAddressList());
if (result.get("code").equals(200))
{
diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml
index 3fa5ad3..844fc1e 100644
--- a/ruoyi-system/pom.xml
+++ b/ruoyi-system/pom.xml
@@ -23,6 +23,11 @@
<artifactId>ruoyi-common</artifactId>
</dependency>
<dependency>
+ <groupId>com.ruoyi</groupId>
+ <artifactId>dryad-payment</artifactId>
+ <version>1.0.0</version>
+ </dependency>
+ <dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.1</version>
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/config/MapServiceConfiguration.java b/ruoyi-system/src/main/java/com/ruoyi/system/config/MapServiceConfiguration.java
new file mode 100644
index 0000000..66c619a
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/config/MapServiceConfiguration.java
@@ -0,0 +1,54 @@
+package com.ruoyi.system.config;
+
+import com.ruoyi.common.config.MapServiceConfig;
+import com.ruoyi.system.service.IMapService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+/**
+ * 鍦板浘鏈嶅姟閰嶇疆绫�
+ * 鏍规嵁閰嶇疆鍔ㄦ�侀�夋嫨浣跨敤鐧惧害鍦板浘杩樻槸澶╁湴鍥�
+ *
+ * @author ruoyi
+ */
+@Configuration
+public class MapServiceConfiguration {
+
+ private static final Logger logger = LoggerFactory.getLogger(MapServiceConfiguration.class);
+
+ @Autowired
+ private MapServiceConfig mapServiceConfig;
+
+ @Autowired
+ @Qualifier("baiduMapService")
+ private IMapService baiduMapService;
+
+ @Autowired
+ @Qualifier("tiandituMapService")
+ private IMapService tiandituMapService;
+
+ /**
+ * 鏍规嵁閰嶇疆鎻愪緵涓昏鐨勫湴鍥炬湇鍔″疄鐜�
+ *
+ * @return 鍦板浘鏈嶅姟瀹炰緥
+ */
+ @Bean
+ @Primary
+ public IMapService mapService() {
+ String provider = mapServiceConfig.getProvider();
+
+ if ("tianditu".equalsIgnoreCase(provider)) {
+ logger.info("浣跨敤澶╁湴鍥炬湇鍔¤繘琛屽湴鐞嗙紪鐮�");
+ return tiandituMapService;
+ } else {
+ // 榛樿浣跨敤鐧惧害鍦板浘
+ logger.info("浣跨敤鐧惧害鍦板浘鏈嶅姟杩涜鍦扮悊缂栫爜");
+ return baiduMapService;
+ }
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/DepartmentSyncDTO.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/DepartmentSyncDTO.java
index a3ca439..ed1be91 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/DepartmentSyncDTO.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/DepartmentSyncDTO.java
@@ -20,6 +20,7 @@
/** 鐖堕儴闂ㄥ悕绉� */
private String parentName;
+
public Integer getDepartmentId()
{
return departmentId;
@@ -60,6 +61,7 @@
this.parentName = parentName;
}
+
@Override
public String toString()
{
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/PaidMoney.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/PaidMoney.java
new file mode 100644
index 0000000..7b0bc8b
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/PaidMoney.java
@@ -0,0 +1,191 @@
+package com.ruoyi.system.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+/**
+ * 鏃х郴缁熸敮浠樿褰曞璞� PaidMoney
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+public class PaidMoney {
+ private static final long serialVersionUID = 1L;
+
+ /** 涓婚敭ID */
+ private Long id;
+
+ /** 鏀粯绫诲埆锛岄粯璁I */
+ private String paidMoneyClass;
+
+ /** 鏈嶅姟璁㈠崟ID */
+ private Long serviceOrdIDDt;
+
+ /** 璋冨害璁㈠崟ID */
+ private Long dispatchOrdIDDt;
+
+ /** 鏀粯閲戦 */
+ private BigDecimal paidMoney;
+
+ /** 鏀粯绫诲瀷 */
+ private Integer paidMoneyType;
+
+ /** 鏀粯鍗曞彿锛屾牸寮忥細浜ゆ槗娴佹按[鏀粯涓撶敤] */
+ private String paidMoneyMono;
+
+ /** 鏀粯鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date paidMoneyTime;
+
+ /** 鏀粯浜篛AID */
+ private Integer paidMoneyOaID;
+
+ /** 鍗曚綅ID锛岄粯璁や负0 */
+ private Integer paidMoneyUnitID;
+
+ /** 纭ID */
+ private Integer paidMoneyAPID;
+
+ /** 纭鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date paidMoneyAPTime;
+
+ /** 纭鐘舵�� 1琛ㄧず宸茬‘璁わ紝0琛ㄧず鏈‘璁� */
+ private Integer paidMoneyAPCheck;
+
+ /** 纭鏃堕棿鎴� */
+ private String paidMoneyTimestamp;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getPaidMoneyClass() {
+ return paidMoneyClass;
+ }
+
+ public void setPaidMoneyClass(String paidMoneyClass) {
+ this.paidMoneyClass = paidMoneyClass;
+ }
+
+ public Long getServiceOrdIDDt() {
+ return serviceOrdIDDt;
+ }
+
+ public void setServiceOrdIDDt(Long serviceOrdIDDt) {
+ this.serviceOrdIDDt = serviceOrdIDDt;
+ }
+
+ public Long getDispatchOrdIDDt() {
+ return dispatchOrdIDDt;
+ }
+
+ public void setDispatchOrdIDDt(Long dispatchOrdIDDt) {
+ this.dispatchOrdIDDt = dispatchOrdIDDt;
+ }
+
+ public BigDecimal getPaidMoney() {
+ return paidMoney;
+ }
+
+ public void setPaidMoney(BigDecimal paidMoney) {
+ this.paidMoney = paidMoney;
+ }
+
+ public Integer getPaidMoneyType() {
+ return paidMoneyType;
+ }
+
+ public void setPaidMoneyType(Integer paidMoneyType) {
+ this.paidMoneyType = paidMoneyType;
+ }
+
+ public String getPaidMoneyMono() {
+ return paidMoneyMono;
+ }
+
+ public void setPaidMoneyMono(String paidMoneyMono) {
+ this.paidMoneyMono = paidMoneyMono;
+ }
+
+ public Date getPaidMoneyTime() {
+ return paidMoneyTime;
+ }
+
+ public void setPaidMoneyTime(Date paidMoneyTime) {
+ this.paidMoneyTime = paidMoneyTime;
+ }
+
+ public Integer getPaidMoneyOaID() {
+ return paidMoneyOaID;
+ }
+
+ public void setPaidMoneyOaID(Integer paidMoneyOaID) {
+ this.paidMoneyOaID = paidMoneyOaID;
+ }
+
+ public Integer getPaidMoneyUnitID() {
+ return paidMoneyUnitID;
+ }
+
+ public void setPaidMoneyUnitID(Integer paidMoneyUnitID) {
+ this.paidMoneyUnitID = paidMoneyUnitID;
+ }
+
+ public Integer getPaidMoneyAPID() {
+ return paidMoneyAPID;
+ }
+
+ public void setPaidMoneyAPID(Integer paidMoneyAPID) {
+ this.paidMoneyAPID = paidMoneyAPID;
+ }
+
+ public Date getPaidMoneyAPTime() {
+ return paidMoneyAPTime;
+ }
+
+ public void setPaidMoneyAPTime(Date paidMoneyAPTime) {
+ this.paidMoneyAPTime = paidMoneyAPTime;
+ }
+
+ public Integer getPaidMoneyAPCheck() {
+ return paidMoneyAPCheck;
+ }
+
+ public void setPaidMoneyAPCheck(Integer paidMoneyAPCheck) {
+ this.paidMoneyAPCheck = paidMoneyAPCheck;
+ }
+
+ public String getPaidMoneyTimestamp() {
+ return paidMoneyTimestamp;
+ }
+
+ public void setPaidMoneyTimestamp(String paidMoneyTimestamp) {
+ this.paidMoneyTimestamp = paidMoneyTimestamp;
+ }
+
+ @Override
+ public String toString() {
+ return "PaidMoney{" +
+ "id=" + id +
+ ", paidMoneyClass='" + paidMoneyClass + '\'' +
+ ", serviceOrdIDDt=" + serviceOrdIDDt +
+ ", dispatchOrdIDDt=" + dispatchOrdIDDt +
+ ", paidMoney=" + paidMoney +
+ ", paidMoneyType=" + paidMoneyType +
+ ", paidMoneyMono='" + paidMoneyMono + '\'' +
+ ", paidMoneyTime=" + paidMoneyTime +
+ ", paidMoneyOaID=" + paidMoneyOaID +
+ ", paidMoneyUnitID=" + paidMoneyUnitID +
+ ", paidMoneyAPID=" + paidMoneyAPID +
+ ", paidMoneyAPTime=" + paidMoneyAPTime +
+ ", paidMoneyAPCheck=" + paidMoneyAPCheck +
+ ", paidMoneyTimestamp='" + paidMoneyTimestamp + '\'' +
+ '}';
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/PaidMoneyAdd.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/PaidMoneyAdd.java
new file mode 100644
index 0000000..713b191
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/PaidMoneyAdd.java
@@ -0,0 +1,118 @@
+package com.ruoyi.system.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+/**
+ * 鏃х郴缁熼檮鍔犺垂鐢ㄨ褰曞璞� PaidMoney_Add
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+public class PaidMoneyAdd {
+ private static final long serialVersionUID = 1L;
+
+ /** 涓婚敭ID */
+ private Long id;
+
+ /** 鏈嶅姟鍗旾D */
+ private Long toServiceOrdID;
+
+ /** 璋冨害鍗旾D */
+ private Long toDispatchOrdID;
+
+ /** 闄勫姞璐圭敤绫诲瀷 */
+ private Integer addMoneyType;
+
+ /** 闄勫姞璐圭敤 */
+ private BigDecimal addMoney;
+
+ /** 闄勫姞璐圭敤璇存槑 */
+ private String addMoneyExplain;
+
+ /** 闄勫姞璐圭敤鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date addMoneyTime;
+
+ /** 娣诲姞鐢ㄦ埛鐨凮AID */
+ private Integer addMoneyOAID;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getToServiceOrdID() {
+ return toServiceOrdID;
+ }
+
+ public void setToServiceOrdID(Long toServiceOrdID) {
+ this.toServiceOrdID = toServiceOrdID;
+ }
+
+ public Long getToDispatchOrdID() {
+ return toDispatchOrdID;
+ }
+
+ public void setToDispatchOrdID(Long toDispatchOrdID) {
+ this.toDispatchOrdID = toDispatchOrdID;
+ }
+
+ public Integer getAddMoneyType() {
+ return addMoneyType;
+ }
+
+ public void setAddMoneyType(Integer addMoneyType) {
+ this.addMoneyType = addMoneyType;
+ }
+
+ public BigDecimal getAddMoney() {
+ return addMoney;
+ }
+
+ public void setAddMoney(BigDecimal addMoney) {
+ this.addMoney = addMoney;
+ }
+
+ public String getAddMoneyExplain() {
+ return addMoneyExplain;
+ }
+
+ public void setAddMoneyExplain(String addMoneyExplain) {
+ this.addMoneyExplain = addMoneyExplain;
+ }
+
+ public Date getAddMoneyTime() {
+ return addMoneyTime;
+ }
+
+ public void setAddMoneyTime(Date addMoneyTime) {
+ this.addMoneyTime = addMoneyTime;
+ }
+
+ public Integer getAddMoneyOAID() {
+ return addMoneyOAID;
+ }
+
+ public void setAddMoneyOAID(Integer addMoneyOAID) {
+ this.addMoneyOAID = addMoneyOAID;
+ }
+
+ @Override
+ public String toString() {
+ return "PaidMoneyAdd{" +
+ "id=" + id +
+ ", toServiceOrdID=" + toServiceOrdID +
+ ", toDispatchOrdID=" + toDispatchOrdID +
+ ", addMoneyType=" + addMoneyType +
+ ", addMoney=" + addMoney +
+ ", addMoneyExplain='" + addMoneyExplain + '\'' +
+ ", addMoneyTime=" + addMoneyTime +
+ ", addMoneyOAID=" + addMoneyOAID +
+ '}';
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java
index bf7fe22..377d933 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java
@@ -123,6 +123,9 @@
/** 鎿嶄綔鏃ュ織鍒楄〃 */
private List<SysTaskLog> operationLogs;
+ /** 鎵ц浜哄憳鍒楄〃 */
+ private List<SysTaskAssignee> assignees;
+
/** 鎬ユ晳杞繍鎵╁睍淇℃伅 */
private SysTaskEmergency emergencyInfo;
@@ -361,6 +364,14 @@
return welfareInfo;
}
+ public void setAssignees(List<SysTaskAssignee> assignees) {
+ this.assignees = assignees;
+ }
+
+ public List<SysTaskAssignee> getAssignees() {
+ return assignees;
+ }
+
/**
* 鍒ゆ柇鏄惁鍙互鍙樻洿鐘舵��
*/
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAdditionalFee.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAdditionalFee.java
new file mode 100644
index 0000000..0dac7dc
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAdditionalFee.java
@@ -0,0 +1,168 @@
+package com.ruoyi.system.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 浠诲姟闄勫姞璐圭敤瀵硅薄 sys_task_additional_fee
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+public class SysTaskAdditionalFee extends BaseEntity {
+ private static final long serialVersionUID = 1L;
+
+ /** 涓婚敭ID */
+ private Long id;
+
+ /** 浠诲姟ID */
+ private Long taskId;
+
+ /** 璐圭敤绫诲瀷(瀛楀吀task_additional_fee_type) */
+ private String feeType;
+
+ /** 璐圭敤鍚嶇О */
+ private String feeName;
+
+ /** 鍗曚环 */
+ private BigDecimal unitAmount;
+
+ /** 鏁伴噺 */
+ private Integer quantity;
+
+ /** 鎬婚噾棰� */
+ private BigDecimal totalAmount;
+
+ /** 鍒涘缓鑰� */
+ private String createdBy;
+
+ /** 鍒涘缓鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date createdTime;
+
+ /** 鏃х郴缁熼檮鍔犺垂鐢ㄨ褰旾D(PaidMoney_Add.id) */
+ private Long pid;
+
+ /** 鍚屾鐘舵�侊細0鏈悓姝ワ紝1鍚屾涓紝2鍚屾鎴愬姛锛�3鍚屾澶辫触 */
+ private Integer syncStatus;
+
+ /** 鍚屾鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date syncTime;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getTaskId() {
+ return taskId;
+ }
+
+ public void setTaskId(Long taskId) {
+ this.taskId = taskId;
+ }
+
+ public String getFeeType() {
+ return feeType;
+ }
+
+ public void setFeeType(String feeType) {
+ this.feeType = feeType;
+ }
+
+ public String getFeeName() {
+ return feeName;
+ }
+
+ public void setFeeName(String feeName) {
+ this.feeName = feeName;
+ }
+
+ public BigDecimal getUnitAmount() {
+ return unitAmount;
+ }
+
+ public void setUnitAmount(BigDecimal unitAmount) {
+ this.unitAmount = unitAmount;
+ }
+
+ public Integer getQuantity() {
+ return quantity;
+ }
+
+ public void setQuantity(Integer quantity) {
+ this.quantity = quantity;
+ }
+
+ public BigDecimal getTotalAmount() {
+ return totalAmount;
+ }
+
+ public void setTotalAmount(BigDecimal totalAmount) {
+ this.totalAmount = totalAmount;
+ }
+
+ public String getCreatedBy() {
+ return createdBy;
+ }
+
+ public void setCreatedBy(String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public Date getCreatedTime() {
+ return createdTime;
+ }
+
+ public void setCreatedTime(Date createdTime) {
+ this.createdTime = createdTime;
+ }
+
+ public Long getPid() {
+ return pid;
+ }
+
+ public void setPid(Long pid) {
+ this.pid = pid;
+ }
+
+ public Integer getSyncStatus() {
+ return syncStatus;
+ }
+
+ public void setSyncStatus(Integer syncStatus) {
+ this.syncStatus = syncStatus;
+ }
+
+ public Date getSyncTime() {
+ return syncTime;
+ }
+
+ public void setSyncTime(Date syncTime) {
+ this.syncTime = syncTime;
+ }
+
+ @Override
+ public String toString() {
+ return "SysTaskAdditionalFee{" +
+ "id=" + id +
+ ", taskId=" + taskId +
+ ", feeType='" + feeType + '\'' +
+ ", feeName='" + feeName + '\'' +
+ ", unitAmount=" + unitAmount +
+ ", quantity=" + quantity +
+ ", totalAmount=" + totalAmount +
+ ", createdBy='" + createdBy + '\'' +
+ ", createdTime=" + createdTime +
+ ", pid=" + pid +
+ ", syncStatus=" + syncStatus +
+ ", syncTime=" + syncTime +
+ '}';
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskEmergency.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskEmergency.java
index 325700d..d4522e8 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskEmergency.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskEmergency.java
@@ -129,6 +129,9 @@
/** 璋冨害鍗曞悓姝ラ敊璇俊鎭� */
private String dispatchSyncErrorMsg;
+ /** 鏄惁闇�瑕侀噸鏂板悓姝ワ細0-涓嶉渶瑕侊紝1-闇�瑕侀噸鏂板悓姝ワ紙杞﹁締鎴栦汉鍛樺彉鏇达級 */
+ private Integer needResync;
+
public Long getId() {
@@ -443,6 +446,14 @@
this.dispatchSyncErrorMsg = dispatchSyncErrorMsg;
}
+ public Integer getNeedResync() {
+ return needResync;
+ }
+
+ public void setNeedResync(Integer needResync) {
+ this.needResync = needResync;
+ }
+
@Override
public String toString() {
return "SysTaskEmergency{" +
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskPayment.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskPayment.java
new file mode 100644
index 0000000..fa0debe
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskPayment.java
@@ -0,0 +1,267 @@
+package com.ruoyi.system.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 浠诲姟鏀粯璁板綍瀵硅薄 sys_task_payment
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+public class SysTaskPayment extends BaseEntity {
+ private static final long serialVersionUID = 1L;
+
+ /** 涓婚敭ID */
+ private Long id;
+
+ /** 浠诲姟ID */
+ private Long taskId;
+
+ /** 鎬婚噾棰�(鎴愪氦浠�+闄勫姞璐�) */
+ private BigDecimal totalAmount;
+
+ /** 缁撶畻閲戦 */
+ private BigDecimal settlementAmount;
+
+ /** 鏀粯鏂瑰紡:CASH鐜伴噾,ON_ACCOUNT鎸傚笎,WECHAT寰俊,ALIPAY鏀粯瀹� */
+ private String paymentMethod;
+
+ /** 鏀粯鐘舵��:UNPAID鏈敮浠�,PENDING寰呮敮浠�,PAID宸叉敮浠�,FAILED澶辫触,REFUNDED宸查��娆� */
+ private String payStatus;
+
+ /** 鏀粯鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date payTime;
+
+ /** 鍟嗘埛璁㈠崟鍙� */
+ private String outTradeNo;
+
+ /** 涓夋柟浜ゆ槗鍙� */
+ private String tradeNo;
+
+ /** 浜岀淮鐮侀摼鎺� */
+ private String codeUrl;
+
+ /** 浜岀淮鐮佽繃鏈熸椂闂� */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date qrExpireTime;
+
+ /** 鏀粯鎻愪緵鍟�:WECHAT,ALIPAY */
+ private String provider;
+
+ /** 鏀粯妯″潡杩斿洖鐨勫敮涓�鏍囪瘑 */
+ private String paymentRefId;
+
+ /** 鍥炶皟鍦板潃 */
+ private String callbackUrl;
+
+ /** 鍒涘缓鑰� */
+ private String createdBy;
+
+ /** 鍒涘缓鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date createdTime;
+
+ /** 鏇存柊鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date updateTime;
+
+ /** 鏃х郴缁熸敮浠樿褰旾D锛圥aidMoney.id锛� */
+ private Long pid;
+
+ /** 鍚屾鐘舵�侊細0鏈悓姝ワ紝1鍚屾涓紝2鍚屾鎴愬姛锛�3鍚屾澶辫触 */
+ private Integer syncStatus;
+
+ /** 鍚屾鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date syncTime;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getTaskId() {
+ return taskId;
+ }
+
+ public void setTaskId(Long taskId) {
+ this.taskId = taskId;
+ }
+
+ public BigDecimal getTotalAmount() {
+ return totalAmount;
+ }
+
+ public void setTotalAmount(BigDecimal totalAmount) {
+ this.totalAmount = totalAmount;
+ }
+
+ public BigDecimal getSettlementAmount() {
+ return settlementAmount;
+ }
+
+ public void setSettlementAmount(BigDecimal settlementAmount) {
+ this.settlementAmount = settlementAmount;
+ }
+
+ public String getPaymentMethod() {
+ return paymentMethod;
+ }
+
+ public void setPaymentMethod(String paymentMethod) {
+ this.paymentMethod = paymentMethod;
+ }
+
+ public String getPayStatus() {
+ return payStatus;
+ }
+
+ public void setPayStatus(String payStatus) {
+ this.payStatus = payStatus;
+ }
+
+ public Date getPayTime() {
+ return payTime;
+ }
+
+ public void setPayTime(Date payTime) {
+ this.payTime = payTime;
+ }
+
+ public String getOutTradeNo() {
+ return outTradeNo;
+ }
+
+ public void setOutTradeNo(String outTradeNo) {
+ this.outTradeNo = outTradeNo;
+ }
+
+ public String getTradeNo() {
+ return tradeNo;
+ }
+
+ public void setTradeNo(String tradeNo) {
+ this.tradeNo = tradeNo;
+ }
+
+ public String getCodeUrl() {
+ return codeUrl;
+ }
+
+ public void setCodeUrl(String codeUrl) {
+ this.codeUrl = codeUrl;
+ }
+
+ public Date getQrExpireTime() {
+ return qrExpireTime;
+ }
+
+ public void setQrExpireTime(Date qrExpireTime) {
+ this.qrExpireTime = qrExpireTime;
+ }
+
+ public String getProvider() {
+ return provider;
+ }
+
+ public void setProvider(String provider) {
+ this.provider = provider;
+ }
+
+ public String getPaymentRefId() {
+ return paymentRefId;
+ }
+
+ public void setPaymentRefId(String paymentRefId) {
+ this.paymentRefId = paymentRefId;
+ }
+
+ public String getCallbackUrl() {
+ return callbackUrl;
+ }
+
+ public void setCallbackUrl(String callbackUrl) {
+ this.callbackUrl = callbackUrl;
+ }
+
+ public String getCreatedBy() {
+ return createdBy;
+ }
+
+ public void setCreatedBy(String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public Date getCreatedTime() {
+ return createdTime;
+ }
+
+ public void setCreatedTime(Date createdTime) {
+ this.createdTime = createdTime;
+ }
+
+ public Date getUpdateTime() {
+ return updateTime;
+ }
+
+ public void setUpdateTime(Date updateTime) {
+ this.updateTime = updateTime;
+ }
+
+ public Long getPid() {
+ return pid;
+ }
+
+ public void setPid(Long pid) {
+ this.pid = pid;
+ }
+
+ public Integer getSyncStatus() {
+ return syncStatus;
+ }
+
+ public void setSyncStatus(Integer syncStatus) {
+ this.syncStatus = syncStatus;
+ }
+
+ public Date getSyncTime() {
+ return syncTime;
+ }
+
+ public void setSyncTime(Date syncTime) {
+ this.syncTime = syncTime;
+ }
+
+ @Override
+ public String toString() {
+ return "SysTaskPayment{" +
+ "id=" + id +
+ ", taskId=" + taskId +
+ ", totalAmount=" + totalAmount +
+ ", settlementAmount=" + settlementAmount +
+ ", paymentMethod='" + paymentMethod + '\'' +
+ ", payStatus='" + payStatus + '\'' +
+ ", payTime=" + payTime +
+ ", outTradeNo='" + outTradeNo + '\'' +
+ ", tradeNo='" + tradeNo + '\'' +
+ ", codeUrl='" + codeUrl + '\'' +
+ ", qrExpireTime=" + qrExpireTime +
+ ", provider='" + provider + '\'' +
+ ", paymentRefId='" + paymentRefId + '\'' +
+ ", callbackUrl='" + callbackUrl + '\'' +
+ ", createdBy='" + createdBy + '\'' +
+ ", createdTime=" + createdTime +
+ ", updateTime=" + updateTime +
+ ", pid=" + pid +
+ ", syncStatus=" + syncStatus +
+ ", syncTime=" + syncTime +
+ '}';
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskPaymentCreateVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskPaymentCreateVO.java
new file mode 100644
index 0000000..e13501d
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskPaymentCreateVO.java
@@ -0,0 +1,56 @@
+package com.ruoyi.system.domain.vo;
+
+import java.math.BigDecimal;
+
+/**
+ * 鍒涘缓鏀粯VO
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+public class TaskPaymentCreateVO {
+
+ /** 浠诲姟ID */
+ private Long taskId;
+
+ /** 鏀粯鏂瑰紡 */
+ private String paymentMethod;
+
+ /** 缁撶畻閲戦 */
+ private BigDecimal settlementAmount;
+
+ /** 澶囨敞 */
+ private String remark;
+
+ public Long getTaskId() {
+ return taskId;
+ }
+
+ public void setTaskId(Long taskId) {
+ this.taskId = taskId;
+ }
+
+ public String getPaymentMethod() {
+ return paymentMethod;
+ }
+
+ public void setPaymentMethod(String paymentMethod) {
+ this.paymentMethod = paymentMethod;
+ }
+
+ public BigDecimal getSettlementAmount() {
+ return settlementAmount;
+ }
+
+ public void setSettlementAmount(BigDecimal settlementAmount) {
+ this.settlementAmount = settlementAmount;
+ }
+
+ public String getRemark() {
+ return remark;
+ }
+
+ public void setRemark(String remark) {
+ this.remark = remark;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskPaymentInfoVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskPaymentInfoVO.java
new file mode 100644
index 0000000..3cb192d
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskPaymentInfoVO.java
@@ -0,0 +1,158 @@
+package com.ruoyi.system.domain.vo;
+
+import java.math.BigDecimal;
+import java.util.List;
+import com.ruoyi.system.domain.SysTaskAdditionalFee;
+import com.ruoyi.system.domain.SysTaskPayment;
+
+/**
+ * 浠诲姟鏀粯淇℃伅VO
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+public class TaskPaymentInfoVO {
+
+ /** 浠诲姟缂栧彿 */
+ private String taskCode;
+
+ /** 浠诲姟绫诲瀷 */
+ private String taskType;
+
+ /** 杞﹁締淇℃伅 */
+ private String vehicleInfo;
+
+ /** 鍑哄彂鍦� */
+ private String departureAddress;
+
+ /** 鐩殑鍦� */
+ private String destinationAddress;
+
+ /** 鎴愪氦浠� */
+ private BigDecimal transferPrice;
+
+ /** 闄勫姞璐圭敤鍒楄〃 */
+ private List<SysTaskAdditionalFee> additionalFees;
+
+ /** 闄勫姞璐圭敤姹囨�� */
+ private BigDecimal additionalAmount;
+
+ /** 鎬婚噾棰� */
+ private BigDecimal totalAmount;
+
+ /** 宸叉敮浠橀噾棰� */
+ private BigDecimal paidAmount;
+
+ /** 宸叉敮浠樿褰曞垪琛� */
+ private List<SysTaskPayment> paidPayments;
+
+ /** 鏈�杩戜竴娆℃敮浠樿褰� */
+ private SysTaskPayment latestPayment;
+
+ /** 鏀粯鏂瑰紡鍒楄〃 */
+ private List<String> paymentMethods;
+
+ public String getTaskCode() {
+ return taskCode;
+ }
+
+ public void setTaskCode(String taskCode) {
+ this.taskCode = taskCode;
+ }
+
+ public String getTaskType() {
+ return taskType;
+ }
+
+ public void setTaskType(String taskType) {
+ this.taskType = taskType;
+ }
+
+ public String getVehicleInfo() {
+ return vehicleInfo;
+ }
+
+ public void setVehicleInfo(String vehicleInfo) {
+ this.vehicleInfo = vehicleInfo;
+ }
+
+ public String getDepartureAddress() {
+ return departureAddress;
+ }
+
+ public void setDepartureAddress(String departureAddress) {
+ this.departureAddress = departureAddress;
+ }
+
+ public String getDestinationAddress() {
+ return destinationAddress;
+ }
+
+ public void setDestinationAddress(String destinationAddress) {
+ this.destinationAddress = destinationAddress;
+ }
+
+ public BigDecimal getTransferPrice() {
+ return transferPrice;
+ }
+
+ public void setTransferPrice(BigDecimal transferPrice) {
+ this.transferPrice = transferPrice;
+ }
+
+ public List<SysTaskAdditionalFee> getAdditionalFees() {
+ return additionalFees;
+ }
+
+ public void setAdditionalFees(List<SysTaskAdditionalFee> additionalFees) {
+ this.additionalFees = additionalFees;
+ }
+
+ public BigDecimal getAdditionalAmount() {
+ return additionalAmount;
+ }
+
+ public void setAdditionalAmount(BigDecimal additionalAmount) {
+ this.additionalAmount = additionalAmount;
+ }
+
+ public BigDecimal getTotalAmount() {
+ return totalAmount;
+ }
+
+ public void setTotalAmount(BigDecimal totalAmount) {
+ this.totalAmount = totalAmount;
+ }
+
+ public BigDecimal getPaidAmount() {
+ return paidAmount;
+ }
+
+ public void setPaidAmount(BigDecimal paidAmount) {
+ this.paidAmount = paidAmount;
+ }
+
+ public List<SysTaskPayment> getPaidPayments() {
+ return paidPayments;
+ }
+
+ public void setPaidPayments(List<SysTaskPayment> paidPayments) {
+ this.paidPayments = paidPayments;
+ }
+
+ public SysTaskPayment getLatestPayment() {
+ return latestPayment;
+ }
+
+ public void setLatestPayment(SysTaskPayment latestPayment) {
+ this.latestPayment = latestPayment;
+ }
+
+ public List<String> getPaymentMethods() {
+ return paymentMethods;
+ }
+
+ public void setPaymentMethods(List<String> paymentMethods) {
+ this.paymentMethods = paymentMethods;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskPaymentResultVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskPaymentResultVO.java
new file mode 100644
index 0000000..a09add2
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskPaymentResultVO.java
@@ -0,0 +1,81 @@
+package com.ruoyi.system.domain.vo;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+/**
+ * 鏀粯缁撴灉VO
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+public class TaskPaymentResultVO {
+
+ /** 鏀粯ID */
+ private Long paymentId;
+
+ /** 鏀粯鐘舵�� */
+ private String payStatus;
+
+ /** 浜岀淮鐮侀摼鎺�(浠呭井淇�/鏀粯瀹�) */
+ private String codeUrl;
+
+ /** 浜岀淮鐮佽繃鏈熸椂闂� */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date qrExpireTime;
+
+ /** 浜ゆ槗鍙�(鏀粯鎴愬姛鍚�) */
+ private String tradeNo;
+
+ /** 鏀粯鏃堕棿(鏀粯鎴愬姛鍚�) */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date payTime;
+
+ public Long getPaymentId() {
+ return paymentId;
+ }
+
+ public void setPaymentId(Long paymentId) {
+ this.paymentId = paymentId;
+ }
+
+ public String getPayStatus() {
+ return payStatus;
+ }
+
+ public void setPayStatus(String payStatus) {
+ this.payStatus = payStatus;
+ }
+
+ public String getCodeUrl() {
+ return codeUrl;
+ }
+
+ public void setCodeUrl(String codeUrl) {
+ this.codeUrl = codeUrl;
+ }
+
+ public Date getQrExpireTime() {
+ return qrExpireTime;
+ }
+
+ public void setQrExpireTime(Date qrExpireTime) {
+ this.qrExpireTime = qrExpireTime;
+ }
+
+ public String getTradeNo() {
+ return tradeNo;
+ }
+
+ public void setTradeNo(String tradeNo) {
+ this.tradeNo = tradeNo;
+ }
+
+ public Date getPayTime() {
+ return payTime;
+ }
+
+ public void setPayTime(Date payTime) {
+ this.payTime = payTime;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskUpdateVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskUpdateVO.java
index 9197068..5bad9f2 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskUpdateVO.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskUpdateVO.java
@@ -63,6 +63,9 @@
/** 鐥呮儏ID鍒楄〃锛堢敤浜庡悓姝ヨ皟搴﹀崟鐨凮rdICD_ID鍙傛暟锛� */
private List<Long> diseaseIds;
+ /** 鎵ц浜哄憳鍒楄〃锛堝寘鍚鑹茬被鍨嬶級 */
+ private List<AssigneeInfo> assignees;
+
/** 鎬ユ晳杞繍浠诲姟鎵╁睍淇℃伅 */
private EmergencyInfoVO emergencyInfo;
@@ -200,6 +203,14 @@
public void setEmergencyInfo(EmergencyInfoVO emergencyInfo) {
this.emergencyInfo = emergencyInfo;
+ }
+
+ public List<AssigneeInfo> getAssignees() {
+ return assignees;
+ }
+
+ public void setAssignees(List<AssigneeInfo> assignees) {
+ this.assignees = assignees;
}
/**
@@ -471,4 +482,42 @@
this.transferPrice = transferPrice;
}
}
+
+ /**
+ * 鎵ц浜哄憳淇℃伅鍐呴儴绫�
+ */
+ public static class AssigneeInfo {
+ /** 鐢ㄦ埛ID */
+ private Long userId;
+
+ /** 鐢ㄦ埛濮撳悕 */
+ private String userName;
+
+ /** 鐢ㄦ埛绫诲瀷锛堣鑹诧級锛歞river-鍙告満, doctor-鍖荤敓, nurse-鎶ゅ+ */
+ private String userType;
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public void setUserId(Long userId) {
+ this.userId = userId;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public String getUserType() {
+ return userType;
+ }
+
+ public void setUserType(String userType) {
+ this.userType = userType;
+ }
+ }
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/event/PaymentSuccessEventListener.java b/ruoyi-system/src/main/java/com/ruoyi/system/event/PaymentSuccessEventListener.java
new file mode 100644
index 0000000..9b5b34f
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/event/PaymentSuccessEventListener.java
@@ -0,0 +1,52 @@
+package com.ruoyi.system.event;
+
+import com.ruoyi.payment.domain.event.PaymentSuccessEvent;
+import com.ruoyi.payment.domain.model.PaymentOrder;
+import com.ruoyi.payment.domain.model.PaymentTransaction;
+import com.ruoyi.system.service.ISysTaskPaymentService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鏀粯鎴愬姛浜嬩欢鐩戝惉鍣�
+ * 灏嗘敮浠樻ā鍧楃殑浜嬩欢鍥炶皟杞崲涓轰笟鍔¤〃鏇存柊
+ */
+@Component
+public class PaymentSuccessEventListener {
+
+ private static final Logger log = LoggerFactory.getLogger(PaymentSuccessEventListener.class);
+
+ @Autowired
+ private ISysTaskPaymentService taskPaymentService;
+
+ /**
+ * 鐩戝惉 dryad-payment 鐨勬敮浠樻垚鍔熶簨浠�
+ */
+ @EventListener
+ public void handlePaymentSuccess(PaymentSuccessEvent event) {
+ PaymentOrder order = event.getOrder();
+ PaymentTransaction transaction = event.getTransaction();
+
+ String outTradeNo = order.getBizOrderId();
+ String tradeNo = order.getChannelTradeNo();
+ String channel = order.getChannel();
+ String provider = "WECHAT";
+ if ("ALIPAY".equalsIgnoreCase(channel)) {
+ provider = "ALIPAY";
+ }
+
+ try {
+ boolean success = taskPaymentService.handlePaymentCallback(outTradeNo, tradeNo, provider);
+ if (success) {
+ log.info("[浜嬩欢鐩戝惉] 涓氬姟鏀粯鏇存柊鎴愬姛: bizOrderId={}, tradeNo={}, provider={}", outTradeNo, tradeNo, provider);
+ } else {
+ log.warn("[浜嬩欢鐩戝惉] 涓氬姟鏀粯鏇存柊澶辫触: bizOrderId={}, tradeNo={}, provider={}", outTradeNo, tradeNo, provider);
+ }
+ } catch (Exception e) {
+ log.error("[浜嬩欢鐩戝惉] 澶勭悊鏀粯鎴愬姛浜嬩欢寮傚父", e);
+ }
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DepartmentSyncMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DepartmentSyncMapper.java
index e8b3e01..e2a78c2 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DepartmentSyncMapper.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DepartmentSyncMapper.java
@@ -6,6 +6,7 @@
import org.apache.ibatis.annotations.Param;
import java.util.List;
+import java.util.Map;
/**
* 閮ㄩ棬鍚屾Mapper鎺ュ彛
@@ -29,4 +30,11 @@
* @return 杞繍閮ㄥ瓙閮ㄩ棬鍒楄〃
*/
List<DepartmentSyncDTO> selectTransportDepartments();
+
+ /**
+ * 鏌ヨ閮ㄩ棬鍦板潃淇℃伅
+ *
+ * @return 閮ㄩ棬鍦板潃淇℃伅鍒楄〃
+ */
+ List<Map<String, Object>> selectDepartAddress();
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/PaidMoneyAddMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/PaidMoneyAddMapper.java
new file mode 100644
index 0000000..6be2886
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/PaidMoneyAddMapper.java
@@ -0,0 +1,68 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.common.annotation.DataSource;
+import com.ruoyi.common.enums.DataSourceType;
+import com.ruoyi.system.domain.PaidMoneyAdd;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 鏃х郴缁熼檮鍔犺垂鐢ㄨ褰昅apper鎺ュ彛
+ * 璁块棶SQL Server鏃х郴缁熸暟鎹簱
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+@DataSource(DataSourceType.SQLSERVER)
+public interface PaidMoneyAddMapper {
+
+ /**
+ * 鏍规嵁ID鏌ヨ闄勫姞璐圭敤璁板綍
+ *
+ * @param id 涓婚敭
+ * @return 闄勫姞璐圭敤璁板綍
+ */
+ PaidMoneyAdd selectById(Long id);
+
+ /**
+ * 鏍规嵁ServiceOrdID鍜孌ispatchOrdID鏌ヨ闄勫姞璐圭敤璁板綍鍒楄〃
+ *
+ * @param toServiceOrdID 鏈嶅姟璁㈠崟ID
+ * @param toDispatchOrdID 璋冨害璁㈠崟ID
+ * @return 闄勫姞璐圭敤璁板綍鍒楄〃
+ */
+ List<PaidMoneyAdd> selectByOrderIds(@Param("toServiceOrdID") Long toServiceOrdID,
+ @Param("toDispatchOrdID") Long toDispatchOrdID);
+
+ /**
+ * 鏂板闄勫姞璐圭敤璁板綍
+ *
+ * @param paidMoneyAdd 闄勫姞璐圭敤璁板綍
+ * @return 缁撴灉
+ */
+ int insert(PaidMoneyAdd paidMoneyAdd);
+
+ /**
+ * 鏇存柊闄勫姞璐圭敤璁板綍
+ *
+ * @param paidMoneyAdd 闄勫姞璐圭敤璁板綍
+ * @return 缁撴灉
+ */
+ int update(PaidMoneyAdd paidMoneyAdd);
+
+ /**
+ * 鍒犻櫎闄勫姞璐圭敤璁板綍
+ *
+ * @param id 涓婚敭
+ * @return 缁撴灉
+ */
+ int deleteById(Long id);
+
+ /**
+ * 鏌ヨ鏈�杩戝悓姝ョ殑闄勫姞璐圭敤璁板綍锛堢敤浜庡畾鏃朵换鍔℃壒閲忓悓姝ワ級
+ *
+ * @param hours 鏈�杩慛灏忔椂鍐呯殑璁板綍
+ * @return 闄勫姞璐圭敤璁板綍鍒楄〃
+ */
+ List<PaidMoneyAdd> selectRecentRecords(@Param("hours") Integer hours);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/PaidMoneyMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/PaidMoneyMapper.java
new file mode 100644
index 0000000..fcebcac
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/PaidMoneyMapper.java
@@ -0,0 +1,67 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.common.annotation.DataSource;
+import com.ruoyi.common.enums.DataSourceType;
+import com.ruoyi.system.domain.PaidMoney;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 鏃х郴缁熸敮浠樿褰昅apper鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+@DataSource(DataSourceType.SQLSERVER)
+public interface PaidMoneyMapper {
+
+ /**
+ * 鏍规嵁ID鏌ヨ鏀粯璁板綍
+ *
+ * @param id 涓婚敭
+ * @return 鏀粯璁板綍
+ */
+ PaidMoney selectById(Long id);
+
+ /**
+ * 鏍规嵁ServiceOrdIDDt鍜孌ispatchOrdIDDt鏌ヨ鏀粯璁板綍鍒楄〃
+ *
+ * @param serviceOrdIDDt 鏈嶅姟璁㈠崟ID
+ * @param dispatchOrdIDDt 璋冨害璁㈠崟ID
+ * @return 鏀粯璁板綍鍒楄〃
+ */
+ List<PaidMoney> selectByOrderIds(@Param("serviceOrdIDDt") Long serviceOrdIDDt,
+ @Param("dispatchOrdIDDt") Long dispatchOrdIDDt);
+
+ /**
+ * 鏂板鏀粯璁板綍
+ *
+ * @param paidMoney 鏀粯璁板綍
+ * @return 缁撴灉
+ */
+ int insert(PaidMoney paidMoney);
+
+ /**
+ * 鏇存柊鏀粯璁板綍
+ *
+ * @param paidMoney 鏀粯璁板綍
+ * @return 缁撴灉
+ */
+ int update(PaidMoney paidMoney);
+
+ /**
+ * 鍒犻櫎鏀粯璁板綍
+ *
+ * @param id 涓婚敭
+ * @return 缁撴灉
+ */
+ int deleteById(Long id);
+
+ /**
+ * 鏌ヨ鏈�杩慛澶╃殑鏀粯璁板綍锛堢敤浜庢壒閲忓悓姝ワ級
+ *
+ * @param days 鏈�杩慛澶�
+ * @return 鏀粯璁板綍鍒楄〃
+ */
+ List<PaidMoney> selectRecentRecords(@Param("days") Integer days);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java
index 7b7365a..27e47e0 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java
@@ -36,6 +36,7 @@
*/
public SysDept selectDeptById(Long deptId);
+ public List<SysDept> selectDeptListByParentId(Long parentId);
/**
* 鏍规嵁ID鏌ヨ鎵�鏈夊瓙閮ㄩ棬
*
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskAdditionalFeeMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskAdditionalFeeMapper.java
new file mode 100644
index 0000000..bb4249e
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskAdditionalFeeMapper.java
@@ -0,0 +1,75 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import com.ruoyi.system.domain.SysTaskAdditionalFee;
+
+/**
+ * 浠诲姟闄勫姞璐圭敤Mapper鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+public interface SysTaskAdditionalFeeMapper {
+
+ /**
+ * 鏌ヨ浠诲姟闄勫姞璐圭敤鍒楄〃
+ *
+ * @param taskId 浠诲姟ID
+ * @return 闄勫姞璐圭敤鍒楄〃
+ */
+ List<SysTaskAdditionalFee> selectByTaskId(Long taskId);
+
+ /**
+ * 鏂板浠诲姟闄勫姞璐圭敤
+ *
+ * @param fee 闄勫姞璐圭敤
+ * @return 缁撴灉
+ */
+ int insert(SysTaskAdditionalFee fee);
+
+ /**
+ * 鍒犻櫎浠诲姟闄勫姞璐圭敤
+ *
+ * @param id 涓婚敭
+ * @return 缁撴灉
+ */
+ int deleteById(Long id);
+
+ /**
+ * 鏍规嵁浠诲姟ID鍒犻櫎闄勫姞璐圭敤
+ *
+ * @param taskId 浠诲姟ID
+ * @return 缁撴灉
+ */
+ int deleteByTaskId(Long taskId);
+
+ /**
+ * 鏍规嵁pid鏌ヨ闄勫姞璐圭敤
+ *
+ * @param pid 鏃х郴缁熼檮鍔犺垂鐢ㄨ褰旾D
+ * @return 闄勫姞璐圭敤
+ */
+ SysTaskAdditionalFee selectByPid(Long pid);
+
+ /**
+ * 鏇存柊鍚屾淇℃伅
+ *
+ * @param id 闄勫姞璐圭敤ID
+ * @param pid 鏃х郴缁熼檮鍔犺垂鐢ㄨ褰旾D
+ * @param syncStatus 鍚屾鐘舵��
+ * @param syncTime 鍚屾鏃堕棿
+ * @return 缁撴灉
+ */
+ int updateSyncInfo(@Param("id") Long id,
+ @Param("pid") Long pid,
+ @Param("syncStatus") Integer syncStatus,
+ @Param("syncTime") java.util.Date syncTime);
+
+ /**
+ * 鏌ヨ鏈悓姝ョ殑闄勫姞璐圭敤鍒楄〃
+ *
+ * @return 闄勫姞璐圭敤鍒楄〃
+ */
+ List<SysTaskAdditionalFee> selectUnsyncedFees();
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskEmergencyMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskEmergencyMapper.java
index 8a852ec..8684a38 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskEmergencyMapper.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskEmergencyMapper.java
@@ -105,4 +105,14 @@
* @return 鎬ユ晳杞繍浠诲姟鎵╁睍淇℃伅
*/
public SysTaskEmergency selectByLegacyDispatchOrdId(@Param("legacyDispatchOrdId") Long legacyDispatchOrdId);
+
+ /**
+ * 鏌ヨ闇�瑕侀噸鏂板悓姝ョ殑浠诲姟鍒楄〃锛堣溅杈嗘垨浜哄憳鍙樻洿锛�
+ * 鏀寔鍒嗛〉鏌ヨ锛岃繃婊ゅ凡瀹屾垚/宸插彇娑堢殑浠诲姟
+ *
+ * @param offset 鍋忕Щ閲忥紙浠庣鍑犳潯寮�濮嬶級
+ * @param limit 姣忛〉鏁伴噺
+ * @return 鎬ユ晳杞繍浠诲姟鍒楄〃
+ */
+ public List<SysTaskEmergency> selectNeedResyncTasks(@Param("offset") Integer offset, @Param("limit") Integer limit);
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskPaymentMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskPaymentMapper.java
new file mode 100644
index 0000000..8ac9c66
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskPaymentMapper.java
@@ -0,0 +1,108 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysTaskPayment;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 浠诲姟鏀粯Mapper鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+public interface SysTaskPaymentMapper {
+
+ /**
+ * 鏍规嵁浠诲姟ID鏌ヨ鏈�杩戜竴娆″凡鏀粯鐨勮褰�
+ *
+ * @param taskId 浠诲姟ID
+ * @return 鏀粯璁板綍
+ */
+ SysTaskPayment selectLatestPaidByTaskId(Long taskId);
+
+ /**
+ * 鏍规嵁浠诲姟ID鏌ヨ鎵�鏈夊凡鏀粯璁板綍
+ *
+ * @param taskId 浠诲姟ID
+ * @return 宸叉敮浠樿褰曞垪琛�
+ */
+ List<SysTaskPayment> selectAllPaidByTaskId(Long taskId);
+
+ /**
+ * 鏍规嵁浠诲姟ID鏌ヨ鎵�鏈夋敮浠樿褰�
+ *
+ * @param taskId 浠诲姟ID
+ * @return 鏀粯璁板綍鍒楄〃
+ */
+ List<SysTaskPayment> selectByTaskId(Long taskId);
+
+ /**
+ * 鏍规嵁ID鏌ヨ鏀粯璁板綍
+ *
+ * @param id 涓婚敭
+ * @return 鏀粯璁板綍
+ */
+ SysTaskPayment selectById(Long id);
+
+ /**
+ * 鏍规嵁鍟嗘埛璁㈠崟鍙锋煡璇㈡敮浠樿褰�
+ *
+ * @param outTradeNo 鍟嗘埛璁㈠崟鍙�
+ * @return 鏀粯璁板綍
+ */
+ SysTaskPayment selectByOutTradeNo(String outTradeNo);
+
+ /**
+ * 鏂板鏀粯璁板綍
+ *
+ * @param payment 鏀粯璁板綍
+ * @return 缁撴灉
+ */
+ int insert(SysTaskPayment payment);
+
+ /**
+ * 鏇存柊鏀粯璁板綍
+ *
+ * @param payment 鏀粯璁板綍
+ * @return 缁撴灉
+ */
+ int update(SysTaskPayment payment);
+
+ /**
+ * 鏇存柊鏀粯鐘舵��
+ *
+ * @param id 涓婚敭
+ * @param payStatus 鏀粯鐘舵��
+ * @param tradeNo 浜ゆ槗鍙�
+ * @return 缁撴灉
+ */
+ int updatePayStatus(@Param("id") Long id, @Param("payStatus") String payStatus,
+ @Param("tradeNo") String tradeNo);
+
+ /**
+ * 鏍规嵁鏃х郴缁熸敮浠業D鏌ヨ鏂扮郴缁熸敮浠樿褰�
+ *
+ * @param pid 鏃х郴缁烶aidMoney.id
+ * @return 鏀粯璁板綍
+ */
+ SysTaskPayment selectByPid(Long pid);
+
+ /**
+ * 鏇存柊鍚屾淇℃伅锛坧id銆乻yncStatus銆乻yncTime锛�
+ *
+ * @param id 涓婚敭
+ * @param pid 鏃х郴缁熸敮浠業D
+ * @param syncStatus 鍚屾鐘舵��
+ * @param syncTime 鍚屾鏃堕棿
+ * @return 缁撴灉
+ */
+ int updateSyncInfo(@Param("id") Long id, @Param("pid") Long pid,
+ @Param("syncStatus") Integer syncStatus, @Param("syncTime") java.util.Date syncTime);
+
+ /**
+ * 鏌ヨ鏈悓姝ョ殑鏀粯鎴愬姛璁板綍锛堢敤浜庢壒閲忓悓姝ワ級
+ *
+ * @return 鏀粯璁板綍鍒楄〃
+ */
+ List<SysTaskPayment> selectUnsyncedPaidPayments();
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IAdditionalFeeSyncService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IAdditionalFeeSyncService.java
new file mode 100644
index 0000000..561f550
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IAdditionalFeeSyncService.java
@@ -0,0 +1,42 @@
+package com.ruoyi.system.service;
+
+/**
+ * 闄勫姞璐圭敤鍚屾Service鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+public interface IAdditionalFeeSyncService {
+
+ /**
+ * 灏嗘柊绯荤粺闄勫姞璐圭敤鍚屾鍒版棫绯荤粺PaidMoney_Add琛�
+ *
+ * @param feeId 闄勫姞璐圭敤ID
+ * @return 鏄惁鍚屾鎴愬姛
+ */
+ boolean syncAdditionalFeeToLegacy(Long feeId);
+
+ /**
+ * 灏嗘棫绯荤粺PaidMoney_Add璁板綍鍚屾鍒版柊绯荤粺
+ *
+ * @param paidMoneyAddId 鏃х郴缁熼檮鍔犺垂鐢ㄨ褰旾D
+ * @return 鏄惁鍚屾鎴愬姛
+ */
+ boolean syncAdditionalFeeFromLegacy(Long paidMoneyAddId);
+
+ /**
+ * 鎵归噺鍚屾鏂扮郴缁熸湭鍚屾鐨勯檮鍔犺垂鐢ㄥ埌鏃х郴缁�
+ *
+ * @return 鎴愬姛鍚屾鐨勬暟閲�
+ */
+ int batchSyncAdditionalFeeToLegacy();
+
+ /**
+ * 鎵归噺鍚屾鏃х郴缁熸柊澧炵殑闄勫姞璐圭敤鍒版柊绯荤粺
+ * 鐢ㄤ簬瀹氭椂浠诲姟锛屽悓姝ユ渶杩戜竴娈垫椂闂村唴鐨勯檮鍔犺垂鐢ㄨ褰�
+ *
+ * @param hours 鍚屾鏈�杩慛灏忔椂鍐呯殑璁板綍
+ * @return 鎴愬姛鍚屾鐨勬暟閲�
+ */
+ int batchSyncAdditionalFeeFromLegacy(Integer hours);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncDataService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncDataService.java
index c131d29..1939fb7 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncDataService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncDataService.java
@@ -2,6 +2,7 @@
import com.ruoyi.system.domain.DepartmentSyncDTO;
import java.util.List;
+import java.util.Map;
/**
* SQL Server 閮ㄩ棬鏁版嵁鏌ヨ鏈嶅姟鎺ュ彛
@@ -14,6 +15,7 @@
*/
public interface IDepartmentSyncDataService
{
+ List<Map<String, Object>> getAddressList();
/**
* 浠� SQL Server 鏌ヨ鍚堜綔鍗曚綅涓嬬殑鎵�鏈夊垎鍏徃鏁版嵁
*
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncService.java
index b7fdcdd..b0cc57d 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IDepartmentSyncService.java
@@ -5,6 +5,7 @@
import com.ruoyi.system.domain.OrderClassDTO;
import java.util.List;
+import java.util.Map;
/**
* 閮ㄩ棬鍚屾Service鎺ュ彛
@@ -28,8 +29,10 @@
* @param branchDepts 澶栭儴浼犲叆鐨勫垎鍏徃鏁版嵁鍒楄〃
* @return 鍚屾缁撴灉
*/
- AjaxResult syncBranchDepartments(List<DepartmentSyncDTO> branchDepts, List<OrderClassDTO> orderClassDTOs,List<OrderClassDTO> dispatchClassDTOs);
-
+ AjaxResult syncBranchDepartments(List<DepartmentSyncDTO> branchDepts, List<OrderClassDTO> orderClassDTOs,List<OrderClassDTO> dispatchClassDTOs,List<Map<String,Object>> addressList );
+
+
+ public void syncDeptAddress(List<Map<String,Object>> addressList);
/**
* 鍚屾杞繍閮ㄥ拰瀛愰儴闂ㄦ暟鎹紙浣跨敤澶栭儴浼犲叆鐨勬暟鎹簮锛�
*
@@ -40,5 +43,5 @@
* @param transportDepts 澶栭儴浼犲叆鐨勮浆杩愰儴瀛愰儴闂ㄦ暟鎹垪琛�
* @return 鍚屾缁撴灉
*/
- AjaxResult syncTransportDepartments(List<DepartmentSyncDTO> transportDepts);
+ AjaxResult syncTransportDepartments(List<DepartmentSyncDTO> transportDepts,List<Map<String,Object>> addressList);
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ILegacySystemSyncService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ILegacySystemSyncService.java
index b73955e..4f292d9 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ILegacySystemSyncService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ILegacySystemSyncService.java
@@ -47,4 +47,21 @@
* @return 鎴愬姛鍚屾鐨勪换鍔℃暟閲�
*/
int batchSyncPendingDispatchOrders();
+
+ /**
+ * 閲嶆柊鍚屾杞﹁締鍜屼汉鍛樺彉鏇寸殑浠诲姟鍒版棫绯荤粺
+ * 褰撲换鍔$殑杞﹁締淇℃伅鎴栦汉鍛樹俊鎭彂鐢熷彉鏇存椂锛岄渶瑕佽皟鐢ㄦ棫绯荤粺鎺ュ彛閲嶆柊鍚屾
+ * 璋冪敤 admin_save_25.asp 鎺ュ彛
+ *
+ * @param taskId 浠诲姟ID
+ * @return 鏄惁鎴愬姛
+ */
+ boolean resyncDispatchOrderToLegacy(Long taskId);
+
+ /**
+ * 鎵归噺閲嶆柊鍚屾闇�瑕佹洿鏂扮殑璋冨害鍗�
+ *
+ * @return 鎴愬姛鍚屾鐨勪换鍔℃暟閲�
+ */
+ int batchResyncPendingDispatchOrders();
}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IMapService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IMapService.java
new file mode 100644
index 0000000..8866f86
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IMapService.java
@@ -0,0 +1,21 @@
+package com.ruoyi.system.service;
+
+import java.util.Map;
+
+/**
+ * 鍦板浘鏈嶅姟鎺ュ彛
+ * 鏀寔澶氱鍦板浘鏈嶅姟鎻愪緵鍟嗭紙鐧惧害鍦板浘銆佸ぉ鍦板浘绛夛級
+ *
+ * @author ruoyi
+ */
+public interface IMapService {
+
+ /**
+ * 鍦板潃杞珿PS鍧愭爣锛堝湴鐞嗙紪鐮侊級
+ *
+ * @param address 鍦板潃
+ * @param city 鍩庡競锛堝彲閫夛紝鐢ㄤ簬鎻愰珮瑙f瀽鍑嗙‘鎬э級
+ * @return GPS鍧愭爣锛屽寘鍚玪ng鍜宭at锛屽鏋滆幏鍙栧け璐ヨ繑鍥瀗ull
+ */
+ Map<String, Double> geocoding(String address, String city);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IPaymentModuleService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IPaymentModuleService.java
new file mode 100644
index 0000000..e13ef79
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IPaymentModuleService.java
@@ -0,0 +1,36 @@
+package com.ruoyi.system.service;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+/**
+ * 鏀粯妯″潡璋冪敤Service鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+public interface IPaymentModuleService {
+
+ /**
+ * 鍒涘缓鏀粯浜岀淮鐮�
+ *
+ * @param outTradeNo 鍟嗘埛璁㈠崟鍙�
+ * @param amount 鏀粯閲戦
+ * @param provider 鏀粯鎻愪緵鍟�(WECHAT/ALIPAY)
+ * @param subject 璁㈠崟鏍囬
+ * @param attach 闄勫姞鏁版嵁
+ * @param callbackUrl 鍥炶皟鍦板潃
+ * @return 鏀粯妯″潡杩斿洖缁撴灉 {paymentRefId, codeUrl, expireTime}
+ */
+ Map<String, Object> createQrCode(String outTradeNo, BigDecimal amount, String provider,
+ String subject, String attach, String callbackUrl);
+
+ /**
+ * 鏌ヨ鏀粯鐘舵��
+ *
+ * @param paymentRefId 鏀粯妯″潡杩斿洖鐨勫敮涓�鏍囪瘑
+ * @param outTradeNo 鍟嗘埛璁㈠崟鍙�
+ * @return 鏀粯鐘舵�佺粨鏋� {status, tradeNo, payTime}
+ */
+ Map<String, Object> queryPaymentStatus(String paymentRefId, String outTradeNo);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IPaymentSyncService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IPaymentSyncService.java
new file mode 100644
index 0000000..692b53b
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IPaymentSyncService.java
@@ -0,0 +1,44 @@
+package com.ruoyi.system.service;
+
+import com.ruoyi.system.domain.PaidMoney;
+import com.ruoyi.system.domain.SysTaskPayment;
+
+/**
+ * 鏀粯淇℃伅鍚屾Service鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+public interface IPaymentSyncService {
+
+ /**
+ * 灏嗘柊绯荤粺鏀粯璁板綍鍚屾鍒版棫绯荤粺PaidMoney琛�
+ *
+ * @param payment 鏀粯璁板綍
+ * @return 鏄惁鍚屾鎴愬姛
+ */
+ boolean syncPaymentToLegacy(SysTaskPayment payment);
+
+ /**
+ * 灏嗘棫绯荤粺PaidMoney璁板綍鍚屾鍒版柊绯荤粺
+ *
+ * @param paidMoney 鏃х郴缁熸敮浠樿褰�
+ * @return 鏄惁鍚屾鎴愬姛
+ */
+ boolean syncPaymentFromLegacy(PaidMoney paidMoney);
+
+ /**
+ * 鎵归噺鍚屾鏂扮郴缁熸湭鍚屾鐨勬敮浠樿褰曞埌鏃х郴缁�
+ *
+ * @return 鎴愬姛鍚屾鐨勬暟閲�
+ */
+ int batchSyncPaymentToLegacy();
+
+ /**
+ * 鎵归噺鍚屾鏃х郴缁熸柊澧炵殑鏀粯璁板綍鍒版柊绯荤粺
+ * 鐢ㄤ簬瀹氭椂浠诲姟锛屽悓姝ユ渶杩戜竴娈垫椂闂村唴鐨勬敮浠樿褰�
+ *
+ * @return 鎴愬姛鍚屾鐨勬暟閲�
+ */
+ int batchSyncPaymentFromLegacy();
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskEmergencyService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskEmergencyService.java
index 2bf9ce1..c456593 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskEmergencyService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskEmergencyService.java
@@ -88,4 +88,13 @@
* @return 鎬ユ晳杞繍浠诲姟鍒楄〃
*/
public List<SysTaskEmergency> selectSyncedTasksForStatusUpdate(Integer offset, Integer limit);
+
+ /**
+ * 鏍囪浠诲姟闇�瑕侀噸鏂板悓姝ワ紙杞﹁締鎴栦汉鍛樺彉鏇存椂璋冪敤锛�
+ * 褰撲换鍔$殑杞﹁締淇℃伅鎴栨墽琛屼汉鍛樹俊鎭彂鐢熷彉鏇存椂锛岄渶瑕佽皟鐢ㄦ鏂规硶鏍囪闇�瑕侀噸鏂板悓姝�
+ * 鍙湁宸茬粡鍚屾杩囪皟搴﹀崟鐨勪换鍔℃墠浼氳鏍囪
+ *
+ * @param taskId 浠诲姟ID
+ */
+ public void markNeedResync(Long taskId);
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskPaymentService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskPaymentService.java
new file mode 100644
index 0000000..f94cb0c
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskPaymentService.java
@@ -0,0 +1,92 @@
+package com.ruoyi.system.service;
+
+import java.math.BigDecimal;
+import java.util.List;
+import com.ruoyi.system.domain.SysTaskAdditionalFee;
+import com.ruoyi.system.domain.SysTaskPayment;
+import com.ruoyi.system.domain.vo.TaskPaymentInfoVO;
+import com.ruoyi.system.domain.vo.TaskPaymentCreateVO;
+import com.ruoyi.system.domain.vo.TaskPaymentResultVO;
+
+/**
+ * 浠诲姟鏀粯Service鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+public interface ISysTaskPaymentService {
+
+ /**
+ * 鑾峰彇浠诲姟鏀粯淇℃伅
+ *
+ * @param taskId 浠诲姟ID
+ * @return 鏀粯淇℃伅
+ */
+ TaskPaymentInfoVO getPaymentInfo(Long taskId);
+
+ /**
+ * 鏂板闄勫姞璐圭敤
+ *
+ * @param taskId 浠诲姟ID
+ * @param feeType 璐圭敤绫诲瀷
+ * @param feeName 璐圭敤鍚嶇О
+ * @param unitAmount 鍗曚环
+ * @param quantity 鏁伴噺
+ * @param remark 澶囨敞
+ * @return 闄勫姞璐圭敤姹囨��
+ */
+ BigDecimal addAdditionalFee(Long taskId, String feeType, String feeName,
+ BigDecimal unitAmount, Integer quantity, String remark);
+
+ /**
+ * 鍒犻櫎闄勫姞璐圭敤
+ *
+ * @param taskId 浠诲姟ID
+ * @param feeId 璐圭敤ID
+ * @return 闄勫姞璐圭敤姹囨��
+ */
+ BigDecimal removeAdditionalFee(Long taskId, Long feeId);
+
+ /**
+ * 鍒涘缓鏀粯
+ *
+ * @param createVO 鍒涘缓鏀粯VO
+ * @return 鏀粯缁撴灉
+ */
+ TaskPaymentResultVO createPayment(TaskPaymentCreateVO createVO);
+
+ /**
+ * 鏌ヨ鏀粯鐘舵��
+ *
+ * @param taskId 浠诲姟ID
+ * @param paymentId 鏀粯ID(鍙��)
+ * @return 鏀粯缁撴灉
+ */
+ TaskPaymentResultVO getPaymentStatus(Long taskId, Long paymentId);
+
+ /**
+ * 鏀粯鍥炶皟澶勭悊
+ *
+ * @param outTradeNo 鍟嗘埛璁㈠崟鍙�
+ * @param tradeNo 涓夋柟浜ゆ槗鍙�
+ * @param provider 鏀粯鎻愪緵鍟�
+ * @return 鏄惁澶勭悊鎴愬姛
+ */
+ boolean handlePaymentCallback(String outTradeNo, String tradeNo, String provider);
+
+ /**
+ * 鏌ヨ浠诲姟鐨勯檮鍔犺垂鐢ㄥ垪琛�
+ *
+ * @param taskId 浠诲姟ID
+ * @return 闄勫姞璐圭敤鍒楄〃
+ */
+ List<SysTaskAdditionalFee> getAdditionalFees(Long taskId);
+
+ /**
+ * 鏌ヨ浠诲姟鐨勬渶鏂版敮浠樿褰�
+ *
+ * @param taskId 浠诲姟ID
+ * @return 鏀粯璁板綍
+ */
+ SysTaskPayment getLatestPayment(Long taskId);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/AdditionalFeeSyncServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/AdditionalFeeSyncServiceImpl.java
new file mode 100644
index 0000000..2aaeef3
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/AdditionalFeeSyncServiceImpl.java
@@ -0,0 +1,315 @@
+package com.ruoyi.system.service.impl;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+import com.ruoyi.common.core.domain.entity.SysUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.ruoyi.common.annotation.DataSource;
+import com.ruoyi.common.enums.DataSourceType;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.system.domain.PaidMoneyAdd;
+import com.ruoyi.system.domain.SysTask;
+import com.ruoyi.system.domain.SysTaskAdditionalFee;
+import com.ruoyi.system.domain.SysTaskEmergency;
+import com.ruoyi.system.mapper.PaidMoneyAddMapper;
+import com.ruoyi.system.mapper.SysTaskAdditionalFeeMapper;
+import com.ruoyi.system.mapper.SysTaskEmergencyMapper;
+import com.ruoyi.system.mapper.SysTaskMapper;
+import com.ruoyi.system.mapper.SysUserMapper;
+import com.ruoyi.system.service.IAdditionalFeeSyncService;
+
+/**
+ * 闄勫姞璐圭敤鍚屾Service瀹炵幇
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+@Service
+public class AdditionalFeeSyncServiceImpl implements IAdditionalFeeSyncService {
+
+ private static final Logger log = LoggerFactory.getLogger(AdditionalFeeSyncServiceImpl.class);
+
+ @Autowired
+ private SysTaskAdditionalFeeMapper additionalFeeMapper;
+
+ @Autowired
+ private PaidMoneyAddMapper paidMoneyAddMapper;
+
+ @Autowired
+ private SysTaskMapper sysTaskMapper;
+
+ @Autowired
+ private SysTaskEmergencyMapper sysTaskEmergencyMapper;
+
+ @Autowired
+ private SysUserMapper sysUserMapper;
+
+ /**
+ * 闄勫姞璐圭敤绫诲瀷鏄犲皠锛堟柊绯荤粺 -> 鏃х郴缁燂級
+ * 鏂扮郴缁熷瓧鍏稿�硷細1-绛夊緟璐�, 2-鎷呮灦, 3-灞呭ICU, 4-鍖荤枟璁惧
+ * 鏃х郴缁烝ddMoneyType锛氱洿鎺ヤ娇鐢ㄥ瓧鍏稿��
+ */
+ private Integer convertFeeTypeToLegacy(String feeType) {
+ if (feeType == null || feeType.trim().isEmpty()) {
+ return 1; // 榛樿绛夊緟璐�
+ }
+ try {
+ return Integer.parseInt(feeType);
+ } catch (NumberFormatException e) {
+ log.warn("鏃犳晥鐨勮垂鐢ㄧ被鍨�: {}", feeType);
+ return 1; // 榛樿绛夊緟璐�
+ }
+ }
+
+ /**
+ * 闄勫姞璐圭敤绫诲瀷鏄犲皠锛堟棫绯荤粺 -> 鏂扮郴缁燂級
+ */
+ private String convertFeeTypeFromLegacy(Integer addMoneyType) {
+ if (addMoneyType == null) {
+ return "1"; // 榛樿绛夊緟璐�
+ }
+ return String.valueOf(addMoneyType);
+ }
+
+ /**
+ * 鑾峰彇璐圭敤绫诲瀷鍚嶇О
+ */
+ private String getFeeTypeName(String feeType) {
+ switch (feeType) {
+ case "1": return "绛夊緟璐�";
+ case "2": return "鎷呮灦";
+ case "3": return "灞呭ICU";
+ case "4": return "鍖荤枟璁惧";
+ default: return "鍏朵粬璐圭敤";
+ }
+ }
+
+ @Override
+ @Transactional
+ @DataSource(DataSourceType.SQLSERVER)
+ public boolean syncAdditionalFeeToLegacy(Long feeId) {
+ try {
+ // 1. 鏌ヨ鏂扮郴缁熼檮鍔犺垂鐢ㄨ褰�
+ SysTaskAdditionalFee fee = additionalFeeMapper.selectByTaskId(null).stream()
+ .filter(f -> f.getId().equals(feeId))
+ .findFirst()
+ .orElse(null);
+
+ if (fee == null) {
+ log.error("鏂扮郴缁熼檮鍔犺垂鐢ㄨ褰曚笉瀛樺湪锛宖eeId: {}", feeId);
+ return false;
+ }
+
+ // 2. 濡傛灉宸插悓姝ヨ繃锛岃烦杩�
+ if (fee.getPid() != null && fee.getPid() > 0) {
+ log.info("闄勫姞璐圭敤宸插悓姝ワ紝feeId: {}, pid: {}", feeId, fee.getPid());
+ return true;
+ }
+
+ // 3. 鏌ヨ浠诲姟淇℃伅鑾峰彇ServiceOrdID鍜孌ispatchOrdID
+ SysTask task = sysTaskMapper.selectSysTaskByTaskId(fee.getTaskId());
+ if (task == null) {
+ log.error("浠诲姟涓嶅瓨鍦紝taskId: {}", fee.getTaskId());
+ return false;
+ }
+
+ SysTaskEmergency emergency = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(fee.getTaskId());
+ if (emergency == null || emergency.getLegacyServiceOrdId() == null || emergency.getLegacyDispatchOrdId() == null) {
+ log.error("浠诲姟鏈悓姝ュ埌鏃х郴缁熸垨缂哄皯ServiceOrdID/DispatchOrdID锛宼askId: {}", fee.getTaskId());
+ return false;
+ }
+
+ // 4. 鑾峰彇鍒涘缓浜篛A鐢ㄦ埛ID
+ Integer oaUserId = null;
+ try {
+ String createdBy = fee.getCreatedBy();
+ if (createdBy != null) {
+ SysUser user = sysUserMapper.selectUserByUserName(createdBy);
+ if (user != null) {
+ oaUserId = user.getOaUserId();
+ }
+ }
+ } catch (Exception e) {
+ log.warn("鑾峰彇鍒涘缓浜篛A鐢ㄦ埛ID澶辫触锛屼娇鐢ㄩ粯璁ゅ��", e);
+ }
+
+ // 5. 鏋勫缓PaidMoneyAdd瀵硅薄
+ PaidMoneyAdd paidMoneyAdd = new PaidMoneyAdd();
+ paidMoneyAdd.setToServiceOrdID(emergency.getLegacyServiceOrdId());
+ paidMoneyAdd.setToDispatchOrdID(emergency.getLegacyDispatchOrdId());
+ paidMoneyAdd.setAddMoneyType(convertFeeTypeToLegacy(fee.getFeeType()));
+ paidMoneyAdd.setAddMoney(fee.getTotalAmount());
+ paidMoneyAdd.setAddMoneyExplain(fee.getFeeName() +
+ (fee.getRemark() != null && !fee.getRemark().isEmpty() ? "(" + fee.getRemark() + ")" : ""));
+ paidMoneyAdd.setAddMoneyTime(fee.getCreatedTime() != null ? fee.getCreatedTime() : new Date());
+ paidMoneyAdd.setAddMoneyOAID(oaUserId);
+
+ // 6. 鎻掑叆鏃х郴缁烶aidMoney_Add琛�
+ int result = paidMoneyAddMapper.insert(paidMoneyAdd);
+ if (result > 0) {
+ // 7. 鏇存柊鏂扮郴缁熼檮鍔犺垂鐢ㄨ褰曠殑鍚屾淇℃伅
+ additionalFeeMapper.updateSyncInfo(feeId, paidMoneyAdd.getId(), 2, new Date());
+ log.info("闄勫姞璐圭敤鍚屾鍒版棫绯荤粺鎴愬姛锛宖eeId: {}, pid: {}", feeId, paidMoneyAdd.getId());
+ return true;
+ } else {
+ log.error("鎻掑叆鏃х郴缁烶aidMoney_Add琛ㄥけ璐ワ紝feeId: {}", feeId);
+ return false;
+ }
+
+ } catch (Exception e) {
+ log.error("鍚屾闄勫姞璐圭敤鍒版棫绯荤粺寮傚父锛宖eeId: {}", feeId, e);
+ // 鏇存柊鍚屾鐘舵�佷负澶辫触
+ try {
+ additionalFeeMapper.updateSyncInfo(feeId, null, 3, new Date());
+ } catch (Exception ex) {
+ log.error("鏇存柊鍚屾鐘舵�佸け璐�", ex);
+ }
+ return false;
+ }
+ }
+
+ @Override
+ @Transactional
+ public boolean syncAdditionalFeeFromLegacy(Long paidMoneyAddId) {
+ try {
+ // 1. 鏌ヨ鏃х郴缁烶aidMoney_Add璁板綍
+ PaidMoneyAdd paidMoneyAdd = paidMoneyAddMapper.selectById(paidMoneyAddId);
+ if (paidMoneyAdd == null) {
+ log.error("鏃х郴缁熼檮鍔犺垂鐢ㄨ褰曚笉瀛樺湪锛宲aidMoneyAddId: {}", paidMoneyAddId);
+ return false;
+ }
+
+ // 2. 妫�鏌ユ槸鍚﹀凡鍚屾杩�
+ SysTaskAdditionalFee existFee = additionalFeeMapper.selectByPid(paidMoneyAddId);
+ if (existFee != null) {
+ log.info("鏃х郴缁熼檮鍔犺垂鐢ㄨ褰曞凡鍚屾锛宲aidMoneyAddId: {}, feeId: {}", paidMoneyAddId, existFee.getId());
+ return true;
+ }
+
+ // 3. 鏍规嵁ServiceOrdID鏌ヨ鏂扮郴缁熶换鍔�
+ if (paidMoneyAdd.getToServiceOrdID() == null) {
+ log.warn("鏃х郴缁熼檮鍔犺垂鐢ㄨ褰曠己灏慡erviceOrdID锛宲aidMoneyAddId: {}锛岃烦杩囧悓姝�", paidMoneyAddId);
+ return false;
+ }
+
+ SysTaskEmergency emergency = sysTaskEmergencyMapper.selectByLegacyServiceOrdId(paidMoneyAdd.getToServiceOrdID());
+ if (emergency == null) {
+ log.warn("鏈壘鍒板搴旂殑杞繍浠诲姟锛孲erviceOrdID: {}锛岃烦杩囧悓姝�", paidMoneyAdd.getToServiceOrdID());
+ return false;
+ }
+
+ // 4. 楠岃瘉DispatchOrdID鏄惁鍖归厤
+ if (paidMoneyAdd.getToDispatchOrdID() == null) {
+ log.warn("鏃х郴缁熼檮鍔犺垂鐢ㄨ褰曠己灏慏ispatchOrdID锛宲aidMoneyAddId: {}锛岃烦杩囧悓姝�", paidMoneyAddId);
+ return false;
+ }
+
+ if (!paidMoneyAdd.getToDispatchOrdID().equals(emergency.getLegacyDispatchOrdId())) {
+ log.warn("杞繍浠诲姟DispatchOrdID涓嶅尮閰嶏紝ServiceOrdID: {}, 闄勫姞璐圭敤DispatchOrdID: {} vs 浠诲姟DispatchOrdID: {}锛岃烦杩囧悓姝�",
+ paidMoneyAdd.getToServiceOrdID(), paidMoneyAdd.getToDispatchOrdID(), emergency.getLegacyDispatchOrdId());
+ return false;
+ }
+
+ // 5. 鏋勫缓SysTaskAdditionalFee瀵硅薄
+ SysTaskAdditionalFee fee = new SysTaskAdditionalFee();
+ fee.setTaskId(emergency.getTaskId());
+ fee.setFeeType(convertFeeTypeFromLegacy(paidMoneyAdd.getAddMoneyType()));
+ fee.setFeeName(getFeeTypeName(fee.getFeeType()));
+ fee.setUnitAmount(paidMoneyAdd.getAddMoney());
+ fee.setQuantity(1);
+ fee.setTotalAmount(paidMoneyAdd.getAddMoney());
+ fee.setRemark(paidMoneyAdd.getAddMoneyExplain());
+ fee.setCreatedBy("system");
+ fee.setCreatedTime(paidMoneyAdd.getAddMoneyTime() != null ? paidMoneyAdd.getAddMoneyTime() : new Date());
+ fee.setPid(paidMoneyAddId);
+ fee.setSyncStatus(2); // 鍚屾鎴愬姛
+ fee.setSyncTime(new Date());
+
+ // 6. 鎻掑叆鏂扮郴缁熼檮鍔犺垂鐢ㄨ褰�
+ int result = additionalFeeMapper.insert(fee);
+ if (result > 0) {
+ log.info("鏃х郴缁熼檮鍔犺垂鐢ㄨ褰曞悓姝ュ埌鏂扮郴缁熸垚鍔燂紝paidMoneyAddId: {}, feeId: {}", paidMoneyAddId, fee.getId());
+ return true;
+ } else {
+ log.error("鎻掑叆鏂扮郴缁熼檮鍔犺垂鐢ㄨ褰曞け璐ワ紝paidMoneyAddId: {}", paidMoneyAddId);
+ return false;
+ }
+
+ } catch (Exception e) {
+ log.error("鍚屾鏃х郴缁熼檮鍔犺垂鐢ㄨ褰曞埌鏂扮郴缁熷紓甯革紝paidMoneyAddId: {}", paidMoneyAddId, e);
+ return false;
+ }
+ }
+
+ @Override
+ public int batchSyncAdditionalFeeToLegacy() {
+ int successCount = 0;
+ try {
+ // 鏌ヨ鏈悓姝ョ殑闄勫姞璐圭敤璁板綍
+ List<SysTaskAdditionalFee> unsyncedFees = additionalFeeMapper.selectUnsyncedFees();
+
+ log.info("寮�濮嬫壒閲忓悓姝ラ檮鍔犺垂鐢ㄥ埌鏃х郴缁燂紝寰呭悓姝ヨ褰曟暟: {}", unsyncedFees.size());
+
+ for (SysTaskAdditionalFee fee : unsyncedFees) {
+ try {
+ if (syncAdditionalFeeToLegacy(fee.getId())) {
+ successCount++;
+ }
+ // 姣忔潯璁板綍闂撮殧1绉掞紝閬垮厤杩囦簬棰戠箒
+ Thread.sleep(1000);
+ } catch (Exception e) {
+ log.error("鍚屾闄勫姞璐圭敤澶辫触锛宖eeId: {}", fee.getId(), e);
+ }
+ }
+
+ log.info("鎵归噺鍚屾闄勫姞璐圭敤鍒版棫绯荤粺瀹屾垚锛屾垚鍔�: {}, 鎬绘暟: {}", successCount, unsyncedFees.size());
+
+ } catch (Exception e) {
+ log.error("鎵归噺鍚屾闄勫姞璐圭敤鍒版棫绯荤粺寮傚父", e);
+ }
+
+ return successCount;
+ }
+
+ @Override
+ @DataSource(DataSourceType.SQLSERVER)
+ public int batchSyncAdditionalFeeFromLegacy(Integer hours) {
+ int successCount = 0;
+ try {
+ if (hours == null || hours <= 0) {
+ hours = 24; // 榛樿24灏忔椂
+ }
+
+ // 鏌ヨ鏈�杩慛灏忔椂鐨勯檮鍔犺垂鐢ㄨ褰�
+ List<PaidMoneyAdd> recentRecords = paidMoneyAddMapper.selectRecentRecords(hours);
+
+ log.info("寮�濮嬫壒閲忓悓姝ユ棫绯荤粺闄勫姞璐圭敤鍒版柊绯荤粺锛屾渶杩憑}灏忔椂璁板綍鏁�: {}", hours, recentRecords.size());
+
+ for (PaidMoneyAdd paidMoneyAdd : recentRecords) {
+ try {
+ if (syncAdditionalFeeFromLegacy(paidMoneyAdd.getId())) {
+ successCount++;
+ }
+ // 姣忔潯璁板綍闂撮殧1绉掞紝閬垮厤杩囦簬棰戠箒
+ Thread.sleep(1000);
+ } catch (Exception e) {
+ log.error("鍚屾鏃х郴缁熼檮鍔犺垂鐢ㄥけ璐ワ紝paidMoneyAddId: {}", paidMoneyAdd.getId(), e);
+ }
+ }
+
+ log.info("鎵归噺鍚屾鏃х郴缁熼檮鍔犺垂鐢ㄥ埌鏂扮郴缁熷畬鎴愶紝鎴愬姛: {}, 鎬绘暟: {}", successCount, recentRecords.size());
+
+ } catch (Exception e) {
+ log.error("鎵归噺鍚屾鏃х郴缁熼檮鍔犺垂鐢ㄥ埌鏂扮郴缁熷紓甯�", e);
+ }
+
+ return successCount;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/BaiduMapServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/BaiduMapServiceImpl.java
new file mode 100644
index 0000000..f44748a
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/BaiduMapServiceImpl.java
@@ -0,0 +1,85 @@
+package com.ruoyi.system.service.impl;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.common.utils.http.HttpUtils;
+import com.ruoyi.common.config.BaiduMapConfig;
+import com.ruoyi.system.service.IMapService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 鐧惧害鍦板浘鏈嶅姟瀹炵幇
+ *
+ * @author ruoyi
+ */
+@Service("baiduMapService")
+public class BaiduMapServiceImpl implements IMapService {
+
+ private static final Logger logger = LoggerFactory.getLogger(BaiduMapServiceImpl.class);
+
+ @Autowired
+ private BaiduMapConfig baiduMapConfig;
+
+ /**
+ * 鍦板潃杞珿PS鍧愭爣锛堝湴鐞嗙紪鐮侊級
+ *
+ * @param address 鍦板潃
+ * @param city 鍩庡競锛堝彲閫夛紝鐢ㄤ簬鎻愰珮瑙f瀽鍑嗙‘鎬э級
+ * @return GPS鍧愭爣锛屽寘鍚玪ng鍜宭at锛屽鏋滆幏鍙栧け璐ヨ繑鍥瀗ull
+ */
+ @Override
+ public Map<String, Double> geocoding(String address, String city) {
+ // 濡傛灉鍦板潃涓虹┖锛岀洿鎺ヨ繑鍥瀗ull
+ if (address == null || address.trim().isEmpty()) {
+ return null;
+ }
+
+ try {
+ // 鏋勫缓鐧惧害鍦板浘鍦扮悊缂栫爜API URL
+ String url = "https://api.map.baidu.com/geocoding/v3/";
+ String params = "address=" + URLEncoder.encode(address, StandardCharsets.UTF_8.toString()) +
+ (city != null && !city.trim().isEmpty() ?
+ "&city=" + URLEncoder.encode(city, StandardCharsets.UTF_8.toString()) : "") +
+ "&output=json" +
+ "&ak=" + baiduMapConfig.getAk();
+
+ logger.info("鐧惧害鍦板浘鍦扮悊缂栫爜璇锋眰: address={}, city={}", address, city);
+
+ // 鍙戦�丠TTP璇锋眰
+ String response = HttpUtils.sendGet(url, params);
+
+ // 瑙f瀽鍝嶅簲
+ JSONObject jsonObject = JSONObject.parseObject(response);
+ if (jsonObject.getInteger("status") != 0) {
+ logger.warn("鐧惧害鍦板浘鍦扮悊缂栫爜澶辫触: address={}, status={}, message={}",
+ address, jsonObject.getInteger("status"), jsonObject.getString("message"));
+ return null;
+ }
+
+ // 鎻愬彇鍧愭爣
+ JSONObject result = jsonObject.getJSONObject("result");
+ JSONObject location = result.getJSONObject("location");
+ double lng = location.getDouble("lng");
+ double lat = location.getDouble("lat");
+
+ logger.info("鐧惧害鍦板浘鍦扮悊缂栫爜鎴愬姛: address={}, lng={}, lat={}", address, lng, lat);
+
+ Map<String, Double> coordinates = new HashMap<>();
+ coordinates.put("lng", lng);
+ coordinates.put("lat", lat);
+
+ return coordinates;
+ } catch (Exception e) {
+ // 鎹曡幏鎵�鏈夊紓甯革紝閬垮厤褰卞搷涓绘祦绋�
+ logger.error("鐧惧害鍦板浘鍦扮悊缂栫爜寮傚父: address=" + address, e);
+ return null;
+ }
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncDataServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncDataServiceImpl.java
index 79ab877..45f2b7f 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncDataServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncDataServiceImpl.java
@@ -11,6 +11,7 @@
import com.ruoyi.system.service.IDepartmentSyncDataService;
import java.util.List;
+import java.util.Map;
/**
* SQL Server 閮ㄩ棬鏁版嵁鏌ヨ鏈嶅姟瀹炵幇
@@ -31,6 +32,11 @@
@Autowired
private DepartmentSyncMapper departmentSyncMapper;
+
+ public List<Map<String, Object>> getAddressList(){
+ return departmentSyncMapper.selectDepartAddress();
+ }
+
/**
* 浠� SQL Server 鏌ヨ鍚堜綔鍗曚綅涓嬬殑鎵�鏈夊垎鍏徃鏁版嵁
*
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncServiceImpl.java
index 3af352b..2fbce75 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncServiceImpl.java
@@ -1,5 +1,6 @@
package com.ruoyi.system.service.impl;
+import java.math.BigDecimal;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -52,7 +53,7 @@
*/
@Override
@Transactional
- public AjaxResult syncBranchDepartments(List<DepartmentSyncDTO> branchDepts,List<OrderClassDTO> serviceOrderList,List<OrderClassDTO> dispatchOrderList)
+ public AjaxResult syncBranchDepartments(List<DepartmentSyncDTO> branchDepts,List<OrderClassDTO> serviceOrderList,List<OrderClassDTO> dispatchOrderList,List<Map<String, Object>> addressList)
{
try
{
@@ -106,7 +107,7 @@
branchMap.put(branchName, branchDeptId);
// 妫�鏌ュ苟鏇存柊缂栫爜
- syncOrderClassCodes(existingBranch, parts[0].trim(), serviceOrderList, dispatchOrderList);
+ syncOrderClassCodes(existingBranch, parts[0].trim(), serviceOrderList, dispatchOrderList,addressList);
// existingBranch.setDepartmentId(dto.getDepartmentId());
sysDeptMapper.updateDept(existingBranch);
log.info("鏇存柊鍒嗗叕鍙哥紪鐮�: {}, 鏈嶅姟鍗曠紪鐮�: {}, 璋冨害鍗曠紪鐮�: {}",
@@ -125,7 +126,7 @@
// newBranch.setDepartmentId(dto.getDepartmentId());
// 鑷姩鍖归厤骞惰缃湇鍔″崟鍜岃皟搴﹀崟缂栫爜
- syncOrderClassCodes(newBranch, parts[0].trim(), serviceOrderList, dispatchOrderList);
+ syncOrderClassCodes(newBranch, parts[0].trim(), serviceOrderList, dispatchOrderList,addressList);
sysDeptMapper.insertDept(newBranch);
branchDeptId = newBranch.getDeptId();
@@ -204,6 +205,24 @@
return AjaxResult.error("鍚屾澶辫触: " + e.getMessage());
}
}
+
+ /**
+ * 鍚屾鍒嗗叕鍙哥殑鍦板潃鏁版嵁
+ * @param departAddress
+ */
+ public void syncDeptAddress(List<Map<String,Object>> departAddress){
+ List<SysDept> depts =sysDeptMapper.selectDeptListByParentId(100L);
+ for(SysDept dept:depts){
+ Map<String,Object> addressInfo = getDeptAddressInfo(departAddress,dept.getServiceOrderClass());
+ if(addressInfo != null){
+ dept.setDepartureAddress(getAddress(addressInfo));
+ dept.setDepartureLongitude(getLongitude(addressInfo));
+ dept.setDepartureLatitude(getLatitude(addressInfo));
+ }
+ sysDeptMapper.updateDept(dept);
+ }
+
+ }
/**
* 鍚屾杞繍閮ㄥ拰瀛愰儴闂ㄦ暟鎹紙浣跨敤澶栭儴浼犲叆鐨勬暟鎹簮锛�
@@ -218,7 +237,7 @@
*/
@Override
@Transactional
- public AjaxResult syncTransportDepartments(List<DepartmentSyncDTO> transportDepts)
+ public AjaxResult syncTransportDepartments(List<DepartmentSyncDTO> transportDepts, List<Map<String, Object>> addressList)
{
try
{
@@ -347,6 +366,34 @@
}
}
+ private Map<String,Object> getDeptAddressInfo(List<Map<String,Object>> departAddress,String ServiceBranch){
+ return departAddress.stream()
+ .filter(map -> map.get("ServiceBranch").equals(ServiceBranch))
+ .findFirst()
+ .orElse(null);
+ }
+
+ private String getAddress(Map<String, Object> addressInfo){
+ if(addressInfo == null){
+ return null;
+ }
+ return addressInfo.get("ServiceAddress").toString();
+ }
+
+ private BigDecimal getLongitude(Map<String, Object> addressInfo){
+ if(addressInfo == null){
+ return null;
+ }
+ return new BigDecimal(addressInfo.get("ServiceAddress_lng").toString());
+ }
+
+ private BigDecimal getLatitude(Map<String, Object> addressInfo){
+ if(addressInfo == null){
+ return null;
+ }
+ return new BigDecimal(addressInfo.get("ServiceAddress_lat").toString());
+ }
+
/**
* 鍚屾璁㈠崟缂栫爜锛堟湇鍔″崟鍜岃皟搴﹀崟缂栫爜锛�
*
@@ -359,7 +406,7 @@
*/
private void syncOrderClassCodes(SysDept dept, String cityName,
List<OrderClassDTO> serviceOrderList,
- List<OrderClassDTO> dispatchOrderList)
+ List<OrderClassDTO> dispatchOrderList, List<Map<String, Object>> addressList)
{
if (serviceOrderList == null || dispatchOrderList == null)
{
@@ -373,6 +420,19 @@
String serviceOrderClass = matchCityNameToCode(cityName, serviceOrderList);
if (serviceOrderClass != null)
{
+ Map<String, Object> addressInfo = this.getDeptAddressInfo(addressList, serviceOrderClass);
+ String address=this.getAddress(addressInfo);
+ if(address!=null){
+ dept.setDepartureAddress(address);
+ }
+ BigDecimal lon = this.getLongitude(addressInfo);
+ if(lon!=null){
+ dept.setDepartureLongitude(lon);
+ }
+ BigDecimal lat = this.getLatitude(addressInfo);
+ if(lat!=null){
+ dept.setDepartureLatitude(lat);
+ }
dept.setServiceOrderClass(serviceOrderClass);
log.info("鍖归厤鍒版湇鍔″崟缂栫爜 - 鍩庡競: {}, 缂栫爜: {}", cityName, serviceOrderClass);
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacySystemSyncServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacySystemSyncServiceImpl.java
index a58c2c8..10b6db7 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacySystemSyncServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacySystemSyncServiceImpl.java
@@ -58,6 +58,9 @@
private SysTaskMapper sysTaskMapper;
@Autowired
+ private SysTaskEmergencyMapper sysTaskEmergencyMapper;
+
+ @Autowired
private SysUserMapper sysUserMapper;
@Autowired
@@ -252,8 +255,7 @@
/**
* 閲嶆柊鍚屾澶辫触鐨勪换鍔�
*/
- @Override
- @Transactional
+ @Override
public boolean retrySyncTask(Long taskId) {
try {
// 閲嶇疆鍚屾鐘舵��
@@ -353,6 +355,7 @@
// 鏋勫缓璇锋眰鍙傛暟
Map<String, String> params = buildDispatchOrderParams(task, emergency);
+
// 鍙戦�丠TTP璇锋眰
String response = sendHttpPost(legacyConfig.getDispatchCreateUrl(), params);
@@ -521,6 +524,7 @@
params.put("DispatchOrdCoPhone", StringUtils.nvl(emergency.getPatientPhone(), ""));
params.put("ServiceOrdCoName", StringUtils.nvl(emergency.getPatientContact(), ""));
params.put("ServiceOrdCoPhone", StringUtils.nvl(emergency.getPatientPhone(), ""));
+ params.put("ServiceOrdPtName", StringUtils.nvl(emergency.getPatientName(), ""));
// 鍦板潃淇℃伅
params.put("DispatchOrdTraStreet", StringUtils.nvl(task.getDepartureAddress(), StringUtils.nvl(emergency.getHospitalOutAddress(), "")));
@@ -619,7 +623,9 @@
*/
private void syncTaskAssignees(SysTask task, Map<String, String> params) {
try {
- // 鑾峰彇浠诲姟鐨勬墽琛屼汉鍛樹俊鎭垪琛紙鍖呭惈瑙掕壊绫诲瀷锛�
+ // 鑾峰彇浠诲姟鐨勬墽琛屼汉鍛樹俊鎭垪琛紙鍖呭惈瑙掕壊绫诲瀷锛� //TODO 濡傛灉鏈変袱涓徃鏈哄氨瑕� 璁剧疆銆�Entourage_1鍜孍ntourage_2
+ //涓や釜鎶ゅ+灏辫璁剧疆 Entourage_4鍜孍ntourage_6
+ //涓や釜鍖荤敓瑕佽缃� Entourage_3鍜孍ntourage_5
List<TaskCreateVO.AssigneeInfo> assignees = getTaskAssignees(task.getTaskId());
if (assignees == null || assignees.isEmpty()) {
@@ -627,8 +633,12 @@
// 璁剧疆榛樿绌哄��
params.put("EntourageLeadID", "");
params.put("Entourage_1", ""); // 鍙告満
+ params.put("Entourage_2", ""); //鍙告満
params.put("Entourage_3", ""); // 鍖荤敓
+ params.put("Entourage_5", ""); //鍖荤敓
params.put("Entourage_4", ""); // 鎶ゅ+
+ params.put("Entourage_6", ""); // 鎶ゅ+
+
return;
}
@@ -660,6 +670,11 @@
if ("driver".equals(userType)) {
if (driverOaId.isEmpty()) {
driverOaId = oaUserId;
+ if(params.get("Entourage_1")==null) {
+ params.put("Entourage_1", oaUserId);
+ }else{
+ params.put("Entourage_2", oaUserId);
+ }
// 濡傛灉鏄涓�涓墽琛屼汉鍛橈紝璁剧疆涓洪闃�
if (i == 0 && leadEntourageId.isEmpty()) {
leadEntourageId = "1"; // 鍙告満瀵瑰簲Entourage_1
@@ -668,6 +683,11 @@
} else if ("doctor".equals(userType)) {
if (doctorOaId.isEmpty()) {
doctorOaId = oaUserId;
+ if(params.get("Entourage_3")==null) {
+ params.put("Entourage_3", oaUserId);
+ }else{
+ params.put("Entourage_5", oaUserId);
+ }
// 濡傛灉鏄涓�涓墽琛屼汉鍛橈紝璁剧疆涓洪闃�
if (i == 0 && leadEntourageId.isEmpty()) {
leadEntourageId = "3"; // 鍖荤敓瀵瑰簲Entourage_3
@@ -676,6 +696,11 @@
} else if ("nurse".equals(userType)) {
if (nurseOaId.isEmpty()) {
nurseOaId = oaUserId;
+ if(params.get("Entourage_4")==null) {
+ params.put("Entourage_4", oaUserId);
+ }else{
+ params.put("Entourage_6", oaUserId);
+ }
// 濡傛灉鏄涓�涓墽琛屼汉鍛橈紝璁剧疆涓洪闃�
if (i == 0 && leadEntourageId.isEmpty()) {
leadEntourageId = "4"; // 鎶ゅ+瀵瑰簲Entourage_4
@@ -686,9 +711,9 @@
// 璁剧疆鍙傛暟
params.put("EntourageLeadID", leadEntourageId);
- params.put("Entourage_1", driverOaId); // 鍙告満
- params.put("Entourage_3", doctorOaId); // 鍖荤敓
- params.put("Entourage_4", nurseOaId); // 鎶ゅ+
+// params.put("Entourage_1", driverOaId); // 鍙告満
+// params.put("Entourage_3", doctorOaId); // 鍖荤敓
+// params.put("Entourage_4", nurseOaId); // 鎶ゅ+
log.info("浠诲姟鎵ц浜哄憳鍚屾鎴愬姛锛屼换鍔D: {}, 棰嗛槦ID: {}, 鍙告満: {}, 鍖荤敓: {}, 鎶ゅ+: {}",
task.getTaskId(), leadEntourageId, driverOaId, doctorOaId, nurseOaId);
@@ -937,7 +962,7 @@
// 鍦板潃淇℃伅
params.put("province", ""); // 鍑哄彂鍦扮渷浠�
params.put("city", ""); // 鍑哄彂鍦板煄甯�
- params.put("ServiceOrdTraStreet", StringUtils.nvl(task.getDepartureAddress(), StringUtils.nvl(emergency.getHospitalOutAddress(), "")));
+ params.put("ServiceOrdTraStreet",task.getDepartureAddress()); //娲捐溅鍦板潃
params.put("ServiceOrdTraStreetCoo", ""); // 鍑哄彂鍦板潗鏍�
params.put("ServiceOrdTraEnd", StringUtils.nvl(task.getDestinationAddress(), StringUtils.nvl(emergency.getHospitalInAddress(), "")));
params.put("ServiceOrdTraEndCoo", ""); // 鐩殑鍦板潗鏍�
@@ -1109,4 +1134,167 @@
return null;
}
}
+
+ /**
+ * 閲嶆柊鍚屾杞﹁締鍜屼汉鍛樺彉鏇寸殑浠诲姟鍒版棫绯荤粺
+ * 褰撲换鍔$殑杞﹁締淇℃伅鎴栦汉鍛樹俊鎭彂鐢熷彉鏇存椂锛岄渶瑕佽皟鐢ㄦ棫绯荤粺鎺ュ彛閲嶆柊鍚屾
+ * 浣跨敤 admin_save_25.asp 鎺ュ彛锛岃�屼笉鏄� admin_save_24.gds
+ */
+ @Override
+ @Transactional
+ public boolean resyncDispatchOrderToLegacy(Long taskId) {
+ if (!legacyConfig.isEnabled()) {
+ log.info("鏃х郴缁熷悓姝ュ凡绂佺敤锛岃烦杩囪皟搴﹀崟閲嶆柊鍚屾锛屼换鍔D: {}", taskId);
+ return false;
+ }
+
+ try {
+ // 鏌ヨ浠诲姟淇℃伅
+ SysTask task = sysTaskMapper.selectSysTaskByTaskId(taskId);
+ if (task == null) {
+ log.error("浠诲姟涓嶅瓨鍦紝浠诲姟ID: {}", taskId);
+ return false;
+ }
+
+ // 鍙悓姝ユ�ユ晳杞繍浠诲姟
+ if (!"EMERGENCY_TRANSFER".equals(task.getTaskType())) {
+ log.info("闈炴�ユ晳杞繍浠诲姟锛岃烦杩囪皟搴﹀崟閲嶆柊鍚屾锛屼换鍔D: {}", taskId);
+ return false;
+ }
+
+ // 鏌ヨ鎬ユ晳杞繍鎵╁睍淇℃伅
+ SysTaskEmergency emergency = sysTaskEmergencyService.selectSysTaskEmergencyByTaskId(taskId);
+ if (emergency == null) {
+ log.error("鎬ユ晳杞繍鎵╁睍淇℃伅涓嶅瓨鍦紝浠诲姟ID: {}", taskId);
+ return false;
+ }
+
+ // 蹇呴』宸茬粡鍚屾杩囪皟搴﹀崟
+ if (emergency.getLegacyDispatchOrdId() == null || emergency.getLegacyDispatchOrdId() <= 0) {
+ log.warn("璋冨害鍗曟湭鍚屾锛屾棤娉曢噸鏂板悓姝ワ紝浠诲姟ID: {}", taskId);
+ return false;
+ }
+
+ Long serviceOrdId = emergency.getLegacyServiceOrdId();
+ if (serviceOrdId == null || serviceOrdId <= 0) {
+ log.warn("鏈嶅姟鍗曟湭鍚屾锛屾棤娉曢噸鏂板悓姝ヨ皟搴﹀崟锛屼换鍔D: {}", taskId);
+ return false;
+ }
+
+ log.info("寮�濮嬮噸鏂板悓姝ヨ皟搴﹀崟锛屼换鍔D: {}, DispatchOrdID: {}", taskId, emergency.getLegacyDispatchOrdId());
+
+ // 鏋勫缓璇锋眰鍙傛暟锛堜娇鐢ㄧ浉鍚岀殑鍙傛暟鏋勫缓鏂规硶锛�
+ Map<String, String> params = buildDispatchOrderParams(task, emergency);
+ params.put("DispatchOrdID", emergency.getLegacyDispatchOrdId().toString());
+ params.put("ServiceOrdID", emergency.getLegacyServiceOrdId().toString());
+ params.put("DispatchOrdState", "3");
+ // 鍙戦�丠TTP璇锋眰鍒版棫绯荤粺锛堜娇鐢╝dmin_save_25.asp鎺ュ彛锛�
+ String response = sendHttpPost(legacyConfig.getDispatchUpdateUrl(), params);
+
+ // 瑙f瀽鍝嶅簲
+ Long dispatchOrdId = parseResponse(response);
+
+ if (dispatchOrdId != null && dispatchOrdId > 0) {
+ // 閲嶆柊鍚屾鎴愬姛锛屾竻闄ら噸鏂板悓姝ユ爣璁�
+ emergency.setNeedResync(0);
+ emergency.setDispatchSyncTime(new Date());
+ emergency.setDispatchSyncErrorMsg(null);
+ sysTaskEmergencyService.updateSysTaskEmergency(emergency);
+
+ log.info("璋冨害鍗曢噸鏂板悓姝ユ垚鍔燂紝浠诲姟ID: {}, DispatchOrdID: {}", taskId, dispatchOrdId);
+ return true;
+ } else {
+ // 閲嶆柊鍚屾澶辫触
+ emergency.setDispatchSyncErrorMsg("閲嶆柊鍚屾澶辫触锛�" + response);
+ sysTaskEmergencyService.updateSysTaskEmergency(emergency);
+
+ log.error("璋冨害鍗曢噸鏂板悓姝ュけ璐ワ紝浠诲姟ID: {}, 鍝嶅簲: {}", taskId, response);
+ return false;
+ }
+
+ } catch (Exception e) {
+ log.error("閲嶆柊鍚屾璋冨害鍗曞埌鏃х郴缁熷紓甯革紝浠诲姟ID: {}", taskId, e);
+
+ // 鏇存柊鍚屾鐘舵�佷负澶辫触
+ try {
+ SysTaskEmergency emergency = sysTaskEmergencyService.selectSysTaskEmergencyByTaskId(taskId);
+ if (emergency != null) {
+ emergency.setDispatchSyncErrorMsg("閲嶆柊鍚屾寮傚父: " + e.getMessage());
+ sysTaskEmergencyService.updateSysTaskEmergency(emergency);
+ }
+ } catch (Exception ex) {
+ log.error("鏇存柊璋冨害鍗曞悓姝ョ姸鎬佸け璐�", ex);
+ }
+
+ return false;
+ }
+ }
+
+ /**
+ * 鎵归噺閲嶆柊鍚屾闇�瑕佹洿鏂扮殑璋冨害鍗�
+ */
+ @Override
+ public int batchResyncPendingDispatchOrders() {
+ if (!legacyConfig.isEnabled()) {
+ log.info("鏃х郴缁熷悓姝ュ凡绂佺敤");
+ return 0;
+ }
+
+ try {
+ int totalSuccessCount = 0;
+ int pageSize = 100; // 姣忛〉100鏉�
+ int offset = 0;
+
+ while (true) {
+ // 鍒嗛〉鏌ヨ闇�瑕侀噸鏂板悓姝ョ殑浠诲姟
+ List<SysTaskEmergency> needResyncTasks = sysTaskEmergencyMapper.selectNeedResyncTasks(offset, pageSize);
+
+ log.info("鏌ヨ鍒伴渶瑕侀噸鏂板悓姝ョ殑浠诲姟鏁伴噺: {}", needResyncTasks.size());
+ if (needResyncTasks == null || needResyncTasks.isEmpty()) {
+ log.info("娌℃湁鏇村闇�瑕侀噸鏂板悓姝ョ殑浠诲姟锛宱ffset: {}", offset);
+ break; // 娌℃湁鏇村鏁版嵁锛岄��鍑哄惊鐜�
+ }
+
+ log.info("寮�濮嬮噸鏂板悓姝ヨ皟搴﹀崟绗� {} 椤碉紝浠诲姟鏁伴噺: {}", (offset / pageSize) + 1, needResyncTasks.size());
+
+ int pageSuccessCount = 0;
+ for (SysTaskEmergency emergency : needResyncTasks) {
+ log.info("寮�濮嬮噸鏂板悓姝ヨ皟搴﹀崟锛屼换鍔D: {}", emergency.getTaskId());
+ boolean success = resyncDispatchOrderToLegacy(emergency.getTaskId());
+
+ if (success) {
+ pageSuccessCount++;
+ }
+
+ // 閬垮厤杩囦簬棰戠箒鐨勮姹�
+ try {
+ Thread.sleep(1000); // 姣忎釜璇锋眰闂撮殧1绉�
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ log.warn("閲嶆柊鍚屾璋冨害鍗曡涓柇");
+ return totalSuccessCount + pageSuccessCount;
+ }
+ }
+
+ totalSuccessCount += pageSuccessCount;
+ log.info("璋冨害鍗曢噸鏂板悓姝ョ {} 椤靛畬鎴愶紝鎬绘暟: {}, 鎴愬姛: {}",
+ (offset / pageSize) + 1, needResyncTasks.size(), pageSuccessCount);
+
+ // 濡傛灉鏈〉鏁版嵁灏戜簬姣忛〉澶у皬锛岃鏄庡凡缁忔槸鏈�鍚庝竴椤�
+ if (needResyncTasks.size() < pageSize) {
+ log.info("宸插埌杈炬渶鍚庝竴椤碉紝璋冨害鍗曢噸鏂板悓姝ョ粨鏉�");
+ break;
+ }
+
+ offset += pageSize; // 涓嬩竴椤�
+ }
+
+ log.info("鎵归噺閲嶆柊鍚屾璋冨害鍗曞畬鎴愶紝鎬绘垚鍔熸暟: {}", totalSuccessCount);
+ return totalSuccessCount;
+
+ } catch (Exception e) {
+ log.error("鎵归噺閲嶆柊鍚屾璋冨害鍗曞紓甯�", e);
+ return 0;
+ }
+ }
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PaymentModuleServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PaymentModuleServiceImpl.java
new file mode 100644
index 0000000..e57ca71
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PaymentModuleServiceImpl.java
@@ -0,0 +1,118 @@
+package com.ruoyi.system.service.impl;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import org.springframework.stereotype.Service;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.ruoyi.system.service.IPaymentModuleService;
+import com.ruoyi.payment.application.service.PaymentService;
+import com.ruoyi.payment.interfaces.dto.PaymentRequest;
+import com.ruoyi.payment.interfaces.dto.PaymentResponse;
+import com.ruoyi.payment.domain.model.PaymentOrder;
+import com.ruoyi.payment.infrastructure.config.AlipayConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 鏀粯妯″潡璋冪敤Service瀹炵幇
+ * TODO: 瀹為檯椤圭洰涓渶瑕佹浛鎹负鐪熷疄鐨勬敮浠樻ā鍧桯TTP璋冪敤
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+@Service
+public class PaymentModuleServiceImpl implements IPaymentModuleService {
+
+ private static final Logger log = LoggerFactory.getLogger(PaymentModuleServiceImpl.class);
+ @Autowired
+ private PaymentService paymentService;
+
+ @Autowired
+ private AlipayConfig alipayConfig;
+
+ @Value("${payment.module.url:http://localhost:8081/pay}")
+ private String paymentModuleUrl;
+
+ @Override
+ public Map<String, Object> createQrCode(String outTradeNo, BigDecimal amount, String provider,
+ String subject, String attach, String callbackUrl) {
+ log.info("璋冪敤鏀粯妯″潡鍒涘缓浜岀淮鐮�: outTradeNo={}, amount={}, provider={}, subject={}, callbackUrl={}",
+ outTradeNo, amount, provider, subject, callbackUrl);
+
+ try {
+ PaymentRequest request = new PaymentRequest();
+ request.setBizOrderId(outTradeNo);
+ request.setAmount(amount.multiply(new BigDecimal(100)).intValue());
+ request.setSubject(subject);
+ request.setDescription(attach);
+ request.setCallbackUrl(callbackUrl);
+ PaymentResponse response;
+ if (provider != null && "WECHAT".equalsIgnoreCase(provider)) {
+ response = paymentService.createWechatNativePayment(request);
+ } else {
+ // 鏍规嵁閰嶇疆鍐冲畾浣跨敤鍝鏀粯瀹濇敮浠樻柟寮�
+ String paymentMethod = alipayConfig.getPaymentMethod();
+ if ("THIRD_PARTY".equalsIgnoreCase(paymentMethod) || "THIRD-PARTY".equalsIgnoreCase(paymentMethod)) {
+ response = paymentService.createAlipayThirdPartyPrecreate(request);
+ } else {
+ response = paymentService.createAlipayPrecreate(request);
+ }
+ }
+ Map<String, Object> result = new HashMap<>();
+ result.put("paymentRefId", response.getOrderId() != null ? response.getOrderId().toString() : null);
+ result.put("codeUrl", response.getQrBase64());
+ java.time.LocalDateTime expireAt = response.getExpireAt();
+ result.put("expireTime", expireAt != null ? java.util.Date.from(expireAt.atZone(java.time.ZoneId.systemDefault()).toInstant()) : new Date(System.currentTimeMillis() + 2 * 60 * 60 * 1000));
+ return result;
+ } catch (Exception ex) {
+ log.error("璋冪敤鏀粯妯″潡鍒涘缓浜岀淮鐮佸紓甯�", ex);
+ throw ex;
+ }
+ }
+
+ @Override
+ public Map<String, Object> queryPaymentStatus(String paymentRefId, String outTradeNo) {
+ log.info("璋冪敤鏀粯妯″潡鏌ヨ鏀粯鐘舵��: paymentRefId={}, outTradeNo={}", paymentRefId, outTradeNo);
+
+ try {
+ Long orderId = null;
+ try {
+ orderId = paymentRefId != null ? Long.parseLong(paymentRefId) : null;
+ } catch (NumberFormatException e) {
+ log.warn("paymentRefId闈炴暟瀛楋紝鏃犳硶鏌ヨ璁㈠崟: {}", paymentRefId);
+ }
+ Map<String, Object> result = new HashMap<>();
+ if (orderId == null) {
+ result.put("status", "PENDING");
+ result.put("tradeNo", null);
+ result.put("payTime", null);
+ return result;
+ }
+ PaymentOrder order = paymentService.getOrder(orderId);
+ if (order == null) {
+ result.put("status", "PENDING");
+ result.put("tradeNo", null);
+ result.put("payTime", null);
+ return result;
+ }
+ String status = order.getStatus();
+ if ("SUCCEEDED".equalsIgnoreCase(status)) {
+ result.put("status", "PAID");
+ } else if ("FAILED".equalsIgnoreCase(status) || "CANCELED".equalsIgnoreCase(status) || "EXPIRED".equalsIgnoreCase(status)) {
+ result.put("status", "FAILED");
+ } else {
+ result.put("status", "PENDING");
+ }
+ result.put("tradeNo", order.getChannelTradeNo());
+ java.time.LocalDateTime paidAt = order.getPaidAt();
+ result.put("payTime", paidAt != null ? java.util.Date.from(paidAt.atZone(java.time.ZoneId.systemDefault()).toInstant()) : null);
+ return result;
+ } catch (Exception ex) {
+ log.error("璋冪敤鏀粯妯″潡鏌ヨ鏀粯鐘舵�佸紓甯�", ex);
+ throw ex;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PaymentSyncServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PaymentSyncServiceImpl.java
new file mode 100644
index 0000000..2a8e338
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PaymentSyncServiceImpl.java
@@ -0,0 +1,298 @@
+package com.ruoyi.system.service.impl;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.ruoyi.common.annotation.DataSource;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.enums.DataSourceType;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.system.domain.PaidMoney;
+import com.ruoyi.system.domain.SysTask;
+import com.ruoyi.system.domain.SysTaskEmergency;
+import com.ruoyi.system.domain.SysTaskPayment;
+import com.ruoyi.system.mapper.PaidMoneyMapper;
+import com.ruoyi.system.mapper.SysTaskEmergencyMapper;
+import com.ruoyi.system.mapper.SysTaskMapper;
+import com.ruoyi.system.mapper.SysTaskPaymentMapper;
+import com.ruoyi.system.mapper.SysUserMapper;
+import com.ruoyi.system.service.IPaymentSyncService;
+
+/**
+ * 鏀粯淇℃伅鍚屾Service瀹炵幇
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+@Service
+public class PaymentSyncServiceImpl implements IPaymentSyncService {
+
+ private static final Logger log = LoggerFactory.getLogger(PaymentSyncServiceImpl.class);
+
+ @Autowired
+ private SysTaskPaymentMapper sysTaskPaymentMapper;
+
+ @Autowired
+ private PaidMoneyMapper paidMoneyMapper;
+
+ @Autowired
+ private SysTaskMapper sysTaskMapper;
+
+ @Autowired
+ private SysTaskEmergencyMapper sysTaskEmergencyMapper;
+
+ @Autowired
+ private SysUserMapper sysUserMapper;
+
+ /**
+ * 鏀粯鏂瑰紡鏄犲皠锛堟柊绯荤粺 -> 鏃х郴缁燂級
+ * 鏂扮郴缁燂細CASH鐜伴噾, ON_ACCOUNT鎸傝处, WECHAT寰俊, ALIPAY鏀粯瀹�
+ * 鏃х郴缁燂細1-鐜伴噾, 2-閾惰杞处, 3-寰俊鏀粯, 4-鏀粯瀹�, 5-POS鏀舵, 6-鎸傝处, 7-鏄撳尰閫氭寕璐�
+ */
+ private Integer convertPaymentMethodToLegacy(String paymentMethod) {
+ return Integer.valueOf(paymentMethod); // 鐩存帴杞崲锛屽亣璁緋aymentMethod宸茬粡鏄纭殑 legacy 鍊�
+ }
+
+ /**
+ * 鏀粯鏂瑰紡鏄犲皠锛堟棫绯荤粺 -> 鏂扮郴缁燂級
+ */
+ private String convertPaymentMethodFromLegacy(Integer paidMoneyType) {
+ return paidMoneyType.toString(); // 鐩存帴杞崲锛屽亣璁緋aidMoneyType宸茬粡鏄纭殑 paymentMethod 鍊�
+ }
+
+ /**
+ * 灏嗘柊绯荤粺鏀粯璁板綍鍚屾鍒版棫绯荤粺PaidMoney琛�
+ */
+ @Override
+ @Transactional
+ public boolean syncPaymentToLegacy(SysTaskPayment payment) {
+ Long paymentId = payment.getId();
+ try {
+
+
+ // 2. 濡傛灉宸插悓姝ヨ繃锛岃烦杩�
+ if (payment.getPid() != null && payment.getPid() > 0) {
+ log.info("鏀粯璁板綍宸插悓姝ワ紝paymentId: {}, pid: {}", payment.getId(), payment.getPid());
+ return true;
+ }
+
+ // 3. 鏌ヨ浠诲姟淇℃伅鑾峰彇ServiceOrdIDDt鍜孌ispatchOrdIDDt
+ SysTask task = sysTaskMapper.selectSysTaskByTaskId(payment.getTaskId());
+ if (task == null) {
+ log.error("浠诲姟涓嶅瓨鍦紝taskId: {}", payment.getTaskId());
+ return false;
+ }
+
+ SysTaskEmergency emergency = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(payment.getTaskId());
+ if (emergency == null || emergency.getLegacyServiceOrdId() == null || emergency.getLegacyDispatchOrdId() == null) {
+ log.error("浠诲姟鏈悓姝ュ埌鏃х郴缁熸垨缂哄皯ServiceOrdID/DispatchOrdID锛宼askId: {}", payment.getTaskId());
+ return false;
+ }
+
+ // 4. 鑾峰彇鏀粯浜篛A鐢ㄦ埛ID
+ Integer oaUserId = null;
+ try {
+ String createdBy = payment.getCreatedBy();
+ if (createdBy != null) {
+ SysUser user = sysUserMapper.selectUserByUserName(createdBy);
+ if (user != null) {
+ oaUserId = user.getOaUserId();
+ }
+ }
+ } catch (Exception e) {
+ log.warn("鑾峰彇鏀粯浜篛A鐢ㄦ埛ID澶辫触锛屼娇鐢ㄩ粯璁ゅ��", e);
+ }
+
+ // 5. 鏋勫缓PaidMoney瀵硅薄
+ PaidMoney paidMoney = new PaidMoney();
+ paidMoney.setPaidMoneyClass("FI"); // 榛樿FI
+ paidMoney.setServiceOrdIDDt(emergency.getLegacyServiceOrdId());
+ paidMoney.setDispatchOrdIDDt(emergency.getLegacyDispatchOrdId());
+ paidMoney.setPaidMoney(payment.getSettlementAmount());
+ paidMoney.setPaidMoneyType(convertPaymentMethodToLegacy(payment.getPaymentMethod()));
+ paidMoney.setPaidMoneyMono(payment.getTradeNo() != null ? payment.getTradeNo() : payment.getOutTradeNo());
+ paidMoney.setPaidMoneyTime(payment.getPayTime() != null ? payment.getPayTime() : new Date());
+ paidMoney.setPaidMoneyOaID(oaUserId);
+ paidMoney.setPaidMoneyUnitID(0); // 榛樿涓�0
+ paidMoney.setPaidMoneyAPCheck(1); // 宸茬‘璁�
+ paidMoney.setPaidMoneyAPTime(new Date());
+ paidMoney.setPaidMoneyTimestamp(String.valueOf(System.currentTimeMillis()));
+
+ // 6. 鎻掑叆鏃х郴缁烶aidMoney琛�
+ int result = paidMoneyMapper.insert(paidMoney);
+ if (result > 0) {
+ // 7. 鏇存柊鏂扮郴缁熸敮浠樿褰曠殑鍚屾淇℃伅
+ sysTaskPaymentMapper.updateSyncInfo(paymentId, paidMoney.getId(), 2, new Date());
+ log.info("鏀粯璁板綍鍚屾鍒版棫绯荤粺鎴愬姛锛宲aymentId: {}, pid: {}", paymentId, paidMoney.getId());
+ return true;
+ } else {
+ log.error("鎻掑叆鏃х郴缁烶aidMoney琛ㄥけ璐ワ紝paymentId: {}", paymentId);
+ return false;
+ }
+
+ } catch (Exception e) {
+ log.error("鍚屾鏀粯璁板綍鍒版棫绯荤粺寮傚父锛宲aymentId: {}", paymentId, e);
+ // 鏇存柊鍚屾鐘舵�佷负澶辫触
+ try {
+ sysTaskPaymentMapper.updateSyncInfo(paymentId, null, 3, new Date());
+ } catch (Exception ex) {
+ log.error("鏇存柊鍚屾鐘舵�佸け璐�", ex);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * 灏嗘棫绯荤粺PaidMoney璁板綍鍚屾鍒版柊绯荤粺
+ */
+ @Override
+ public boolean syncPaymentFromLegacy(PaidMoney paidMoney) {
+ Long paidMoneyId = paidMoney.getId();
+ try {
+
+
+ // 2. 妫�鏌ユ槸鍚﹀凡鍚屾杩�
+ SysTaskPayment existPayment = sysTaskPaymentMapper.selectByPid(paidMoney.getId());
+ if (existPayment != null) {
+ log.info("鏃х郴缁熸敮浠樿褰曞凡鍚屾锛宲aidMoneyId: {}, paymentId: {}", paidMoney.getId(), existPayment.getId());
+ return true;
+ }
+
+ // 3. 鏍规嵁ServiceOrdIDDt鏌ヨ鏂扮郴缁熶换鍔�
+ if (paidMoney.getServiceOrdIDDt() == null) {
+ log.warn("鏃х郴缁熸敮浠樿褰曠己灏慡erviceOrdID锛宲aidMoneyId: {}锛岃烦杩囧悓姝�", paidMoneyId);
+ return false;
+ }
+
+ SysTaskEmergency emergency = sysTaskEmergencyMapper.selectByLegacyServiceOrdId(paidMoney.getServiceOrdIDDt());
+ if (emergency == null) {
+ log.warn("鏈壘鍒板搴旂殑杞繍浠诲姟锛孲erviceOrdID: {}锛岃烦杩囧悓姝�", paidMoney.getServiceOrdIDDt());
+ return false;
+ }
+
+ // 4. 楠岃瘉DispatchOrdID鏄惁鍖归厤
+ if (paidMoney.getDispatchOrdIDDt() == null) {
+ log.warn("鏃х郴缁熸敮浠樿褰曠己灏慏ispatchOrdID锛宲aidMoneyId: {}锛岃烦杩囧悓姝�", paidMoneyId);
+ return false;
+ }
+
+ if (!paidMoney.getDispatchOrdIDDt().equals(emergency.getLegacyDispatchOrdId())) {
+ log.warn("杞繍浠诲姟DispatchOrdID涓嶅尮閰嶏紝ServiceOrdID: {}, 鏀粯璁板綍DispatchOrdID: {} vs 浠诲姟DispatchOrdID: {}锛岃烦杩囧悓姝�",
+ paidMoney.getServiceOrdIDDt(), paidMoney.getDispatchOrdIDDt(), emergency.getLegacyDispatchOrdId());
+ return false;
+ }
+
+ // 5. 鏌ヨ浠诲姟淇℃伅
+ SysTask task = sysTaskMapper.selectSysTaskByTaskId(emergency.getTaskId());
+ if (task == null) {
+ log.error("浠诲姟涓嶅瓨鍦紝taskId: {}", emergency.getTaskId());
+ return false;
+ }
+
+ // 6. 鏋勫缓SysTaskPayment瀵硅薄
+ SysTaskPayment payment = new SysTaskPayment();
+ payment.setTaskId(task.getTaskId());
+ payment.setTotalAmount(paidMoney.getPaidMoney());
+ payment.setSettlementAmount(paidMoney.getPaidMoney());
+ payment.setPaymentMethod(convertPaymentMethodFromLegacy(paidMoney.getPaidMoneyType()));
+ payment.setPayStatus("PAID"); // 宸叉敮浠�
+ payment.setPayTime(paidMoney.getPaidMoneyTime() != null ? paidMoney.getPaidMoneyTime() : new Date());
+ payment.setOutTradeNo(task.getTaskCode() + "-" + paidMoneyId);
+ payment.setTradeNo(paidMoney.getPaidMoneyMono());
+ payment.setPid(paidMoneyId);
+ payment.setSyncStatus(2); // 鍚屾鎴愬姛
+ payment.setSyncTime(new Date());
+ payment.setCreatedBy("system");
+ payment.setCreatedTime(paidMoney.getPaidMoneyTime() != null ? paidMoney.getPaidMoneyTime() : new Date());
+
+ // 7. 鎻掑叆鏂扮郴缁熸敮浠樿褰�
+ int result = sysTaskPaymentMapper.insert(payment);
+ if (result > 0) {
+ log.info("鏃х郴缁熸敮浠樿褰曞悓姝ュ埌鏂扮郴缁熸垚鍔燂紝paidMoneyId: {}, paymentId: {}", paidMoneyId, payment.getId());
+ return true;
+ } else {
+ log.error("鎻掑叆鏂扮郴缁熸敮浠樿褰曞け璐ワ紝paidMoneyId: {}", paidMoneyId);
+ return false;
+ }
+
+ } catch (Exception e) {
+ log.error("鍚屾鏃х郴缁熸敮浠樿褰曞埌鏂扮郴缁熷紓甯革紝paidMoneyId: {}", paidMoneyId, e);
+ return false;
+ }
+ }
+
+ /**
+ * 鎵归噺鍚屾鏂扮郴缁熸湭鍚屾鐨勬敮浠樿褰曞埌鏃х郴缁�
+ */
+ @Override
+ public int batchSyncPaymentToLegacy() {
+ int successCount = 0;
+ try {
+ // 鏌ヨ鏈悓姝ョ殑鏀粯鎴愬姛璁板綍
+ List<SysTaskPayment> unsyncedPayments = sysTaskPaymentMapper.selectUnsyncedPaidPayments();
+
+ log.info("寮�濮嬫壒閲忓悓姝ユ敮浠樿褰曞埌鏃х郴缁燂紝寰呭悓姝ヨ褰曟暟: {}", unsyncedPayments.size());
+
+ for (SysTaskPayment payment : unsyncedPayments) {
+ try {
+ if (syncPaymentToLegacy(payment)) {
+ successCount++;
+ }
+ // 姣忔潯璁板綍闂撮殧1绉掞紝閬垮厤杩囦簬棰戠箒
+ Thread.sleep(1000);
+ } catch (Exception e) {
+ log.error("鍚屾鏀粯璁板綍澶辫触锛宲aymentId: {}", payment.getId(), e);
+ }
+ }
+
+ log.info("鎵归噺鍚屾鏀粯璁板綍鍒版棫绯荤粺瀹屾垚锛屾垚鍔�: {}, 鎬绘暟: {}", successCount, unsyncedPayments.size());
+
+ } catch (Exception e) {
+ log.error("鎵归噺鍚屾鏀粯璁板綍鍒版棫绯荤粺寮傚父", e);
+ }
+
+ return successCount;
+ }
+
+ /**
+ * 鎵归噺鍚屾鏃х郴缁熸柊澧炵殑鏀粯璁板綍鍒版柊绯荤粺
+ */
+ @Override
+ public int batchSyncPaymentFromLegacy() {
+ int successCount = 0;
+ try {
+ // 鏌ヨ鏃х郴缁熸渶杩�7澶╃殑鏀粯璁板綍
+ List<PaidMoney> recentPayments = paidMoneyMapper.selectRecentRecords(7);
+
+ log.info("寮�濮嬫壒閲忓悓姝ユ棫绯荤粺鏀粯璁板綍鍒版柊绯荤粺锛屾渶杩�7澶╄褰曟暟: {}", recentPayments.size());
+
+ for (PaidMoney paidMoney : recentPayments) {
+ try {
+ if (syncPaymentFromLegacy(paidMoney)) {
+ successCount++;
+ }
+ // 姣忔潯璁板綍闂撮殧1绉掞紝閬垮厤杩囦簬棰戠箒
+ Thread.sleep(1000);
+ } catch (Exception e) {
+ log.error("鍚屾鏃х郴缁熸敮浠樿褰曞け璐ワ紝paidMoneyId: {}", paidMoney.getId(), e);
+ }
+ }
+
+ log.info("鎵归噺鍚屾鏃х郴缁熸敮浠樿褰曞埌鏂扮郴缁熷畬鎴愶紝鎴愬姛: {}, 鎬绘暟: {}", successCount, recentPayments.size());
+
+ } catch (Exception e) {
+ log.error("鎵归噺鍚屾鏃х郴缁熸敮浠樿褰曞埌鏂扮郴缁熷紓甯�", e);
+ }
+
+ return successCount;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskEmergencyServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskEmergencyServiceImpl.java
index a106835..c145c31 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskEmergencyServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskEmergencyServiceImpl.java
@@ -1,5 +1,7 @@
package com.ruoyi.system.service.impl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.system.mapper.SysTaskEmergencyMapper;
@@ -121,4 +123,28 @@
public List<SysTaskEmergency> selectSyncedTasksForStatusUpdate(Integer offset, Integer limit) {
return sysTaskEmergencyMapper.selectSyncedTasksForStatusUpdate(offset, limit);
}
+
+ /**
+ * 鏍囪浠诲姟闇�瑕侀噸鏂板悓姝ワ紙杞﹁締鎴栦汉鍛樺彉鏇存椂璋冪敤锛�
+ */
+ @Override
+ public void markNeedResync(Long taskId) {
+ try {
+ SysTaskEmergency emergency = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(taskId);
+ if (emergency == null) {
+ return;
+ }
+
+ // 鍙湁宸茬粡鍚屾杩囪皟搴﹀崟鐨勪换鍔℃墠闇�瑕佹爣璁伴噸鏂板悓姝�
+ if (emergency.getDispatchSyncStatus() != null && emergency.getDispatchSyncStatus() == 2
+ && emergency.getLegacyDispatchOrdId() != null && emergency.getLegacyDispatchOrdId() > 0) {
+ emergency.setNeedResync(1);
+ sysTaskEmergencyMapper.updateSysTaskEmergency(emergency);
+ }
+ } catch (Exception e) {
+ // 鏍囪澶辫触涓嶅奖鍝嶄富娴佺▼锛屽彧璁板綍鏃ュ織
+ Logger log = LoggerFactory.getLogger(SysTaskEmergencyServiceImpl.class);
+ log.error("鏍囪浠诲姟闇�瑕侀噸鏂板悓姝ュけ璐ワ紝浠诲姟ID: {}", taskId, e);
+ }
+ }
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskPaymentServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskPaymentServiceImpl.java
new file mode 100644
index 0000000..be42618
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskPaymentServiceImpl.java
@@ -0,0 +1,415 @@
+package com.ruoyi.system.service.impl;
+
+import java.math.BigDecimal;
+import java.util.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.domain.SysTask;
+import com.ruoyi.system.domain.SysTaskEmergency;
+import com.ruoyi.system.domain.SysTaskAdditionalFee;
+import com.ruoyi.system.domain.SysTaskPayment;
+import com.ruoyi.system.domain.vo.TaskPaymentInfoVO;
+import com.ruoyi.system.domain.vo.TaskPaymentCreateVO;
+import com.ruoyi.system.domain.vo.TaskPaymentResultVO;
+import com.ruoyi.system.mapper.SysTaskMapper;
+import com.ruoyi.system.mapper.SysTaskEmergencyMapper;
+import com.ruoyi.system.mapper.SysTaskAdditionalFeeMapper;
+import com.ruoyi.system.mapper.SysTaskPaymentMapper;
+import com.ruoyi.system.service.ISysTaskPaymentService;
+import com.ruoyi.system.service.IPaymentModuleService;
+import com.ruoyi.system.service.IPaymentSyncService;
+import com.ruoyi.system.service.IAdditionalFeeSyncService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 浠诲姟鏀粯Service瀹炵幇
+ *
+ * @author ruoyi
+ * @date 2025-01-15
+ */
+@Service
+public class SysTaskPaymentServiceImpl implements ISysTaskPaymentService {
+
+ private static final Logger log = LoggerFactory.getLogger(SysTaskPaymentServiceImpl.class);
+
+ @Autowired
+ private SysTaskMapper sysTaskMapper;
+
+ @Autowired
+ private SysTaskEmergencyMapper sysTaskEmergencyMapper;
+
+ @Autowired
+ private SysTaskAdditionalFeeMapper additionalFeeMapper;
+
+ @Autowired
+ private SysTaskPaymentMapper paymentMapper;
+
+ @Autowired
+ private IPaymentModuleService paymentModuleService;
+
+ @Autowired
+ private IPaymentSyncService paymentSyncService;
+
+ @Autowired
+ private IAdditionalFeeSyncService additionalFeeSyncService;
+
+ /**
+ * 渚涘閮ㄥ簲鐢ㄥ洖璋冪殑鎺ュ彛
+ */
+ @Value("${payment.callback.base-url:http://localhost:8080}")
+ private String callbackBaseUrl;
+
+ @Override
+ public TaskPaymentInfoVO getPaymentInfo(Long taskId) {
+ // 鏌ヨ浠诲姟
+ SysTask task = sysTaskMapper.selectSysTaskByTaskId(taskId);
+ if (task == null) {
+ throw new ServiceException("浠诲姟涓嶅瓨鍦�");
+ }
+
+ // 鍙湁杞繍浠诲姟鎵嶆敮鎸佹敮浠�
+ if (!"EMERGENCY_TRANSFER".equals(task.getTaskType())) {
+ throw new ServiceException("鍙湁杞繍浠诲姟鏀寔鏀粯鍔熻兘");
+ }
+
+ // 鏌ヨ杞繍浠诲姟鎵╁睍淇℃伅鑾峰彇鎴愪氦浠�
+ SysTaskEmergency emergencyInfo = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(taskId);
+ BigDecimal transferPrice = emergencyInfo != null && emergencyInfo.getTransferPrice() != null
+ ? emergencyInfo.getTransferPrice() : BigDecimal.ZERO;
+
+ // 鑾峰彇浠诲姟鍩烘湰淇℃伅
+ String taskCode = task.getTaskCode();
+ String taskType = task.getTaskType();
+
+ // 鑾峰彇杞﹁締淇℃伅
+ String vehicleInfo = "";
+ if (task.getAssignedVehicles() != null && !task.getAssignedVehicles().isEmpty()) {
+ vehicleInfo = task.getAssignedVehicles().get(0).getVehicleNo();
+ if (task.getAssignedVehicles().size() > 1) {
+ vehicleInfo += " 绛�" + task.getAssignedVehicles().size() + "杈�";
+ }
+ }
+
+ // 鑾峰彇鍑哄彂鍦板拰鐩殑鍦帮紙浠庤浆杩愪换鍔℃墿灞曚俊鎭級
+ String departureAddress = "";
+ String destinationAddress = "";
+ if (emergencyInfo != null) {
+ departureAddress = emergencyInfo.getHospitalOutAddress() != null
+ ? emergencyInfo.getHospitalOutAddress() : "";
+ destinationAddress = emergencyInfo.getHospitalInAddress() != null
+ ? emergencyInfo.getHospitalInAddress() : "";
+ }
+
+ // 鏌ヨ闄勫姞璐圭敤
+ List<SysTaskAdditionalFee> additionalFees = additionalFeeMapper.selectByTaskId(taskId);
+
+ // 璁$畻闄勫姞璐圭敤姹囨��
+ BigDecimal additionalAmount = additionalFees.stream()
+ .map(SysTaskAdditionalFee::getTotalAmount)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+ // 璁$畻鎬婚噾棰�
+ BigDecimal totalAmount = transferPrice.add(additionalAmount);
+
+ // 鏌ヨ鎵�鏈夊凡鏀粯璁板綍锛岃绠楀凡鏀粯鎬婚
+ List<SysTaskPayment> paidPayments = paymentMapper.selectAllPaidByTaskId(taskId);
+ BigDecimal paidAmount = paidPayments.stream()
+ .map(SysTaskPayment::getSettlementAmount)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+ // 鏌ヨ鏈�杩戜竴娆℃敮浠樿褰�
+ SysTaskPayment latestPayment = paymentMapper.selectLatestPaidByTaskId(taskId);
+
+ // 鏋勯�犺繑鍥炴暟鎹�
+ TaskPaymentInfoVO vo = new TaskPaymentInfoVO();
+ vo.setTaskCode(taskCode);
+ vo.setTaskType(taskType);
+ vo.setVehicleInfo(vehicleInfo);
+ vo.setDepartureAddress(departureAddress);
+ vo.setDestinationAddress(destinationAddress);
+ vo.setTransferPrice(transferPrice);
+ vo.setAdditionalFees(additionalFees);
+ vo.setAdditionalAmount(additionalAmount);
+ vo.setTotalAmount(totalAmount);
+ vo.setPaidAmount(paidAmount);
+ vo.setPaidPayments(paidPayments); // 璁剧疆宸叉敮浠樿褰曞垪琛�
+ vo.setLatestPayment(latestPayment);
+ vo.setPaymentMethods(Arrays.asList("CASH", "ON_ACCOUNT", "WECHAT", "ALIPAY"));
+
+ return vo;
+ }
+
+ @Override
+ @Transactional
+ public BigDecimal addAdditionalFee(Long taskId, String feeType, String feeName,
+ BigDecimal unitAmount, Integer quantity, String remark) {
+ // 楠岃瘉鍙傛暟
+ if (unitAmount == null || unitAmount.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new ServiceException("鍗曚环蹇呴』澶т簬0");
+ }
+ if (quantity == null || quantity <= 0) {
+ throw new ServiceException("鏁伴噺蹇呴』澶т簬0");
+ }
+
+ // 璁$畻鎬婚噾棰�
+ BigDecimal totalAmount = unitAmount.multiply(new BigDecimal(quantity));
+
+ // 鍒涘缓闄勫姞璐圭敤璁板綍
+ SysTaskAdditionalFee fee = new SysTaskAdditionalFee();
+ fee.setTaskId(taskId);
+ fee.setFeeType(feeType);
+ fee.setFeeName(feeName);
+ fee.setUnitAmount(unitAmount);
+ fee.setQuantity(quantity);
+ fee.setTotalAmount(totalAmount);
+ fee.setRemark(remark);
+ fee.setCreatedBy(SecurityUtils.getUsername());
+
+ additionalFeeMapper.insert(fee);
+
+ // 寮傛鍚屾鍒版棫绯荤粺
+ try {
+ additionalFeeSyncService.syncAdditionalFeeToLegacy(fee.getId());
+ } catch (Exception e) {
+ log.error("鍚屾闄勫姞璐圭敤鍒版棫绯荤粺澶辫触", e);
+ }
+
+ // 杩斿洖褰撳墠闄勫姞璐圭敤姹囨��
+ return calculateAdditionalAmount(taskId);
+ }
+
+ @Override
+ @Transactional
+ public BigDecimal removeAdditionalFee(Long taskId, Long feeId) {
+ additionalFeeMapper.deleteById(feeId);
+ return calculateAdditionalAmount(taskId);
+ }
+
+ @Override
+ @Transactional
+ public TaskPaymentResultVO createPayment(TaskPaymentCreateVO createVO) {
+ Long taskId = createVO.getTaskId();
+ String paymentMethod = createVO.getPaymentMethod();
+ BigDecimal settlementAmount = createVO.getSettlementAmount();
+
+ // 鑾峰彇鏀粯淇℃伅骞舵牎楠�
+ TaskPaymentInfoVO paymentInfo = getPaymentInfo(taskId);
+ BigDecimal paidAmount = paymentInfo.getPaidAmount();
+ BigDecimal remainingAmount = paymentInfo.getTotalAmount().subtract(paidAmount);
+
+ // 鏍¢獙缁撶畻閲戦蹇呴』澶т簬0涓斾笉瓒呰繃鍓╀綑閲戦
+ if (settlementAmount.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new ServiceException("缁撶畻閲戦蹇呴』澶т簬0");
+ }
+ if (settlementAmount.compareTo(remainingAmount) > 0) {
+ throw new ServiceException("缁撶畻閲戦涓嶈兘瓒呰繃鍓╀綑鏈敮浠橀噾棰濓細" + remainingAmount);
+ }
+
+ // 鏌ヨ浠诲姟缂栧彿
+ SysTask task = sysTaskMapper.selectSysTaskByTaskId(taskId);
+ String taskCode = task.getTaskCode();
+
+ // 鐢熸垚鍟嗘埛璁㈠崟鍙�: {taskCode}-{timestampMillis}
+ String outTradeNo = taskCode + "-" + System.currentTimeMillis();
+
+ // 鍒涘缓鏀粯璁板綍
+ SysTaskPayment payment = new SysTaskPayment();
+ payment.setTaskId(taskId);
+ payment.setTotalAmount(paymentInfo.getTotalAmount());
+ payment.setSettlementAmount(settlementAmount);
+ payment.setPaymentMethod(paymentMethod);
+ payment.setOutTradeNo(outTradeNo);
+ payment.setRemark(createVO.getRemark());
+ payment.setCreatedBy(SecurityUtils.getUsername());
+ payment.setCreatedTime(new Date());
+
+ TaskPaymentResultVO result = new TaskPaymentResultVO();
+
+ // 鏀粯鏂瑰紡瀛楀吀鍊硷細1-鐜伴噾, 2-閾惰杞处, 3-寰俊鏀粯, 4-鏀粯瀹�, 5-POS鏀舵, 6-鎸傝处, 7-鏄撳尰閫氭寕璐�
+ // 1-鐜伴噾, 2-閾惰杞处, 5-POS鏀舵, 6-鎸傝处, 7-鏄撳尰閫氭寕璐� 鐩存帴鏍囪涓哄凡鏀粯
+ if ("1".equals(paymentMethod) || "2".equals(paymentMethod) || "5".equals(paymentMethod)
+ || "6".equals(paymentMethod) || "7".equals(paymentMethod)) {
+ payment.setPayStatus("PAID");
+ payment.setPayTime(new Date());
+ payment.setSyncStatus(0); // 鏈悓姝�
+ paymentMapper.insert(payment);
+
+ result.setPaymentId(payment.getId());
+ result.setPayStatus("PAID");
+ result.setPayTime(payment.getPayTime());
+
+ log.info("浠诲姟{}鍒涘缓鏀粯鏂瑰紡{}鏀粯鎴愬姛锛岃鍗曞彿锛歿}", taskId, paymentMethod, outTradeNo);
+
+ // 寮傛鍚屾鍒版棫绯荤粺
+ try {
+ paymentSyncService.syncPaymentToLegacy(payment);
+ } catch (Exception e) {
+ log.error("鍚屾鏀粯璁板綍鍒版棫绯荤粺澶辫触", e);
+ }
+ }
+ // 3-寰俊鏀粯, 4-鏀粯瀹� 闇�瑕佺敓鎴愪簩缁寸爜
+ else if ("3".equals(paymentMethod) || "4".equals(paymentMethod)) {
+ payment.setPayStatus("PENDING");
+ // 璁剧疆鏀粯鎻愪緵鍟嗭細3-WECHAT, 4-ALIPAY
+ String provider = "3".equals(paymentMethod) ? "WECHAT" : "ALIPAY";
+ payment.setProvider(provider);
+
+ // 鐢熸垚鍥炶皟鍦板潃
+ String callbackUrl = callbackBaseUrl + "/payment/callback/" + provider.toLowerCase();
+ payment.setCallbackUrl(callbackUrl);
+
+ try {
+ // 璋冪敤鏀粯妯″潡鍒涘缓浜岀淮鐮�
+ String subject = "鍖荤枟杞繍鏀粯锛�" + taskCode + "锛�";
+ String attach = "taskId:" + taskId;
+
+ Map<String, Object> qrResult = paymentModuleService.createQrCode(
+ outTradeNo, settlementAmount, provider, subject, attach, callbackUrl
+ );
+
+ // 淇濆瓨鏀粯妯″潡杩斿洖鐨勪俊鎭�
+ payment.setPaymentRefId((String) qrResult.get("paymentRefId"));
+ payment.setCodeUrl((String) qrResult.get("codeUrl"));
+ payment.setQrExpireTime((Date) qrResult.get("expireTime"));
+
+ paymentMapper.insert(payment);
+
+ result.setPaymentId(payment.getId());
+ result.setPayStatus("PENDING");
+ result.setCodeUrl(payment.getCodeUrl());
+ result.setQrExpireTime(payment.getQrExpireTime());
+
+ log.info("浠诲姟{}鍒涘缓鏀粯鏂瑰紡{}鏀粯浜岀淮鐮佹垚鍔燂紝璁㈠崟鍙凤細{}锛屼簩缁寸爜锛歿}",
+ taskId, paymentMethod, outTradeNo, payment.getCodeUrl());
+ } catch (Exception e) {
+ log.error("璋冪敤鏀粯妯″潡澶辫触", e);
+ throw new ServiceException("鍒涘缓鏀粯浜岀淮鐮佸け璐ワ細" + e.getMessage());
+ }
+ } else {
+ throw new ServiceException("涓嶆敮鎸佺殑鏀粯鏂瑰紡锛�" + paymentMethod);
+ }
+
+ return result;
+ }
+
+ @Override
+ public TaskPaymentResultVO getPaymentStatus(Long taskId, Long paymentId) {
+ SysTaskPayment payment;
+
+ if (paymentId != null) {
+ payment = paymentMapper.selectById(paymentId);
+ } else {
+ // 鏌ヨ鏈�杩戜竴娆℃敮浠樿褰�
+ List<SysTaskPayment> payments = paymentMapper.selectByTaskId(taskId);
+ if (payments.isEmpty()) {
+ throw new ServiceException("鏈壘鍒版敮浠樿褰�");
+ }
+ payment = payments.get(0);
+ }
+
+ if (payment == null) {
+ throw new ServiceException("鏀粯璁板綍涓嶅瓨鍦�");
+ }
+
+ // 濡傛灉鏄緟鏀粯鐘舵�侊紝鏌ヨ鏀粯妯″潡鑾峰彇鏈�鏂扮姸鎬�
+ if ("PENDING".equals(payment.getPayStatus())) {
+ try {
+ Map<String, Object> statusResult = paymentModuleService.queryPaymentStatus(
+ payment.getPaymentRefId(), payment.getOutTradeNo()
+ );
+
+ String status = (String) statusResult.get("status");
+
+ // 濡傛灉鏀粯鎴愬姛锛屾洿鏂版湰鍦拌褰�
+ if ("PAID".equals(status)) {
+ String tradeNo = (String) statusResult.get("tradeNo");
+ paymentMapper.updatePayStatus(payment.getId(), "PAID", tradeNo);
+ payment.setPayStatus("PAID");
+ payment.setTradeNo(tradeNo);
+ payment.setPayTime(new Date());
+
+ log.info("鏀粯璁板綍{}鐘舵�佹洿鏂颁负宸叉敮浠橈紝浜ゆ槗鍙凤細{}", payment.getId(), tradeNo);
+
+ // 寮傛鍚屾鍒版棫绯荤粺
+ try {
+ paymentSyncService.syncPaymentToLegacy(payment);
+ } catch (Exception ex) {
+ log.error("鍚屾鏀粯璁板綍鍒版棫绯荤粺澶辫触", ex);
+ }
+ }
+ } catch (Exception e) {
+ log.error("鏌ヨ鏀粯鐘舵�佸け璐�", e);
+ }
+ }
+
+ // 鏋勯�犺繑鍥炵粨鏋�
+ TaskPaymentResultVO result = new TaskPaymentResultVO();
+ result.setPaymentId(payment.getId());
+ result.setPayStatus(payment.getPayStatus());
+ result.setCodeUrl(payment.getCodeUrl());
+ result.setQrExpireTime(payment.getQrExpireTime());
+ result.setTradeNo(payment.getTradeNo());
+ result.setPayTime(payment.getPayTime());
+
+ return result;
+ }
+
+ @Override
+ @Transactional
+ public boolean handlePaymentCallback(String outTradeNo, String tradeNo, String provider) {
+ log.info("鏀跺埌鏀粯鍥炶皟锛歰utTradeNo={}, tradeNo={}, provider={}", outTradeNo, tradeNo, provider);
+
+ // 鏌ヨ鏀粯璁板綍
+ SysTaskPayment payment = paymentMapper.selectByOutTradeNo(outTradeNo);
+ if (payment == null) {
+ log.error("鏀粯鍥炶皟澶辫触锛氭湭鎵惧埌璁㈠崟锛宱utTradeNo={}", outTradeNo);
+ return false;
+ }
+
+ // 濡傛灉宸茬粡鏄凡鏀粯鐘舵�侊紝鐩存帴杩斿洖鎴愬姛
+ if ("PAID".equals(payment.getPayStatus())) {
+ log.info("璁㈠崟{}宸叉敮浠橈紝蹇界暐閲嶅鍥炶皟", outTradeNo);
+ return true;
+ }
+
+ // 鏇存柊鏀粯鐘舵��
+ paymentMapper.updatePayStatus(payment.getId(), "PAID", tradeNo);
+
+ log.info("鏀粯鍥炶皟澶勭悊鎴愬姛锛氳鍗晎}宸叉洿鏂颁负宸叉敮浠樼姸鎬�", outTradeNo);
+
+ // 寮傛鍚屾鍒版棫绯荤粺
+ try {
+ paymentSyncService.syncPaymentToLegacy(payment);
+ } catch (Exception e) {
+ log.error("鍚屾鏀粯璁板綍鍒版棫绯荤粺澶辫触", e);
+ }
+
+ return true;
+ }
+
+ @Override
+ public List<SysTaskAdditionalFee> getAdditionalFees(Long taskId) {
+ return additionalFeeMapper.selectByTaskId(taskId);
+ }
+
+ @Override
+ public SysTaskPayment getLatestPayment(Long taskId) {
+ return paymentMapper.selectLatestPaidByTaskId(taskId);
+ }
+
+ /**
+ * 璁$畻闄勫姞璐圭敤姹囨��
+ */
+ private BigDecimal calculateAdditionalAmount(Long taskId) {
+ List<SysTaskAdditionalFee> fees = additionalFeeMapper.selectByTaskId(taskId);
+ return fees.stream()
+ .map(SysTaskAdditionalFee::getTotalAmount)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
index b939e42..ccff010 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
@@ -36,7 +36,9 @@
import com.ruoyi.system.domain.VehicleInfo;
import com.ruoyi.system.service.ISysTaskService;
import com.ruoyi.system.service.ILegacySystemSyncService;
+import com.ruoyi.system.service.ISysTaskEmergencyService;
import com.ruoyi.system.service.ITaskAttachmentSyncService;
+import com.ruoyi.system.service.IMapService;
import com.ruoyi.system.event.TaskCreatedEvent;
import com.ruoyi.system.event.TaskAssignedEvent;
import com.ruoyi.system.event.TaskStatusChangedEvent;
@@ -84,6 +86,9 @@
private ILegacySystemSyncService legacySystemSyncService;
@Autowired
+ private ISysTaskEmergencyService sysTaskEmergencyService;
+
+ @Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
@@ -94,6 +99,9 @@
@Autowired
private SysUserMapper sysUserMapper;
+
+ @Autowired(required = false)
+ private IMapService mapService;
/**
* 鏌ヨ浠诲姟绠$悊
@@ -202,6 +210,44 @@
// 璁剧疆绂忕杞﹀叕閲屾暟
if (createVO.getDistance() != null) {
task.setEstimatedDistance(createVO.getDistance());
+ }
+
+ // 鑷姩鑾峰彇鍑哄彂鍦癎PS鍧愭爣锛堝鏋滅己澶憋級
+ if (task.getDepartureAddress() != null &&
+ (task.getDepartureLongitude() == null || task.getDepartureLatitude() == null) &&
+ mapService != null) {
+ try {
+ Map<String, Double> coords = mapService.geocoding(
+ task.getDepartureAddress(),
+ extractCityFromAddress(task.getDepartureAddress())
+ );
+ if (coords != null) {
+ task.setDepartureLongitude(BigDecimal.valueOf(coords.get("lng")));
+ task.setDepartureLatitude(BigDecimal.valueOf(coords.get("lat")));
+ log.info("鍑哄彂鍦癎PS鍧愭爣鑷姩鑾峰彇鎴愬姛: {}, {}", coords.get("lng"), coords.get("lat"));
+ }
+ } catch (Exception e) {
+ log.error("鑷姩鑾峰彇鍑哄彂鍦癎PS鍧愭爣澶辫触", e);
+ }
+ }
+
+ // 鑷姩鑾峰彇鐩殑鍦癎PS鍧愭爣锛堝鏋滅己澶憋級
+ if (task.getDestinationAddress() != null &&
+ (task.getDestinationLongitude() == null || task.getDestinationLatitude() == null) &&
+ mapService != null) {
+ try {
+ Map<String, Double> coords = mapService.geocoding(
+ task.getDestinationAddress(),
+ extractCityFromAddress(task.getDestinationAddress())
+ );
+ if (coords != null) {
+ task.setDestinationLongitude(BigDecimal.valueOf(coords.get("lng")));
+ task.setDestinationLatitude(BigDecimal.valueOf(coords.get("lat")));
+ log.info("鐩殑鍦癎PS鍧愭爣鑷姩鑾峰彇鎴愬姛: {}, {}", coords.get("lng"), coords.get("lat"));
+ }
+ } catch (Exception e) {
+ log.error("鑷姩鑾峰彇鐩殑鍦癎PS鍧愭爣澶辫触", e);
+ }
}
int result = sysTaskMapper.insertSysTask(task);
@@ -379,6 +425,44 @@
task.setEstimatedDistance(createVO.getDistance());
}
+ // 鑷姩鑾峰彇鍑哄彂鍦癎PS鍧愭爣锛堝鏋滅己澶憋級
+ if (task.getDepartureAddress() != null &&
+ (task.getDepartureLongitude() == null || task.getDepartureLatitude() == null) &&
+ mapService != null) {
+ try {
+ Map<String, Double> coords = mapService.geocoding(
+ task.getDepartureAddress(),
+ extractCityFromAddress(task.getDepartureAddress())
+ );
+ if (coords != null) {
+ task.setDepartureLongitude(BigDecimal.valueOf(coords.get("lng")));
+ task.setDepartureLatitude(BigDecimal.valueOf(coords.get("lat")));
+ log.info("鍑哄彂鍦癎PS鍧愭爣鑷姩鑾峰彇鎴愬姛: {}, {}", coords.get("lng"), coords.get("lat"));
+ }
+ } catch (Exception e) {
+ log.error("鑷姩鑾峰彇鍑哄彂鍦癎PS鍧愭爣澶辫触", e);
+ }
+ }
+
+ // 鑷姩鑾峰彇鐩殑鍦癎PS鍧愭爣锛堝鏋滅己澶憋級
+ if (task.getDestinationAddress() != null &&
+ (task.getDestinationLongitude() == null || task.getDestinationLatitude() == null) &&
+ mapService != null) {
+ try {
+ Map<String, Double> coords = mapService.geocoding(
+ task.getDestinationAddress(),
+ extractCityFromAddress(task.getDestinationAddress())
+ );
+ if (coords != null) {
+ task.setDestinationLongitude(BigDecimal.valueOf(coords.get("lng")));
+ task.setDestinationLatitude(BigDecimal.valueOf(coords.get("lat")));
+ log.info("鐩殑鍦癎PS鍧愭爣鑷姩鑾峰彇鎴愬姛: {}, {}", coords.get("lng"), coords.get("lat"));
+ }
+ } catch (Exception e) {
+ log.error("鑷姩鑾峰彇鐩殑鍦癎PS鍧愭爣澶辫触", e);
+ }
+ }
+
int result = sysTaskMapper.insertSysTask(task);
// 淇濆瓨杞﹁締鍏宠仈淇℃伅
@@ -394,7 +478,7 @@
taskVehicle.setCreateTime(createTime);
taskVehicle.setUpdateBy(userName);
taskVehicle.setUpdateTime(updateTime);
-
+
sysTaskVehicleMapper.insertSysTaskVehicle(taskVehicle);
}
}
@@ -492,10 +576,51 @@
task.setDeptId(updateVO.getDeptId());
}
+ // 鑷姩鑾峰彇鍑哄彂鍦癎PS鍧愭爣锛堝鏋滄洿鏂颁簡鍦板潃浣嗙己澶卞潗鏍囷級
+ if (updateVO.getDepartureAddress() != null &&
+ (updateVO.getDepartureLongitude() == null || updateVO.getDepartureLatitude() == null) &&
+ mapService != null) {
+ try {
+ Map<String, Double> coords = mapService.geocoding(
+ updateVO.getDepartureAddress(),
+ extractCityFromAddress(updateVO.getDepartureAddress())
+ );
+ if (coords != null) {
+ task.setDepartureLongitude(BigDecimal.valueOf(coords.get("lng")));
+ task.setDepartureLatitude(BigDecimal.valueOf(coords.get("lat")));
+ log.info("鍑哄彂鍦癎PS鍧愭爣鑷姩鑾峰彇鎴愬姛: {}, {}", coords.get("lng"), coords.get("lat"));
+ }
+ } catch (Exception e) {
+ log.error("鑷姩鑾峰彇鍑哄彂鍦癎PS鍧愭爣澶辫触", e);
+ }
+ }
+
+ // 鑷姩鑾峰彇鐩殑鍦癎PS鍧愭爣锛堝鏋滄洿鏂颁簡鍦板潃浣嗙己澶卞潗鏍囷級
+ if (updateVO.getDestinationAddress() != null &&
+ (updateVO.getDestinationLongitude() == null || updateVO.getDestinationLatitude() == null) &&
+ mapService != null) {
+ try {
+ Map<String, Double> coords = mapService.geocoding(
+ updateVO.getDestinationAddress(),
+ extractCityFromAddress(updateVO.getDestinationAddress())
+ );
+ if (coords != null) {
+ task.setDestinationLongitude(BigDecimal.valueOf(coords.get("lng")));
+ task.setDestinationLatitude(BigDecimal.valueOf(coords.get("lat")));
+ log.info("鐩殑鍦癎PS鍧愭爣鑷姩鑾峰彇鎴愬姛: {}, {}", coords.get("lng"), coords.get("lat"));
+ }
+ } catch (Exception e) {
+ log.error("鑷姩鑾峰彇鐩殑鍦癎PS鍧愭爣澶辫触", e);
+ }
+ }
+
// 閲嶆柊璁$畻棰勮鍏噷鏁�
calculateEstimatedDistance(task);
int result = sysTaskMapper.updateSysTask(task);
+
+ // 鐢ㄤ簬璺熻釜鏄惁闇�瑕侀噸鏂板悓姝ワ紙杞﹁締銆佷汉鍛樸�佸湴鍧�銆佹垚浜や环鍙樻洿锛�
+ boolean needResync = false;
// 鏇存柊杞﹁締鍏宠仈
if (result > 0 && updateVO.getVehicleIds() != null && !updateVO.getVehicleIds().isEmpty()) {
@@ -525,12 +650,98 @@
taskVehicle.setCreateTime(now);
sysTaskVehicleMapper.insertSysTaskVehicle(taskVehicle);
}
+
+ // 鏍囪闇�瑕侀噸鏂板悓姝ワ紙杞﹁締鍙樻洿锛�
+ needResync = true;
}
}
- // 鏇存柊鎬ユ晳杞繍鎵╁睍淇℃伅
+ // 鏇存柊鎵ц浜哄憳锛堟娴嬩汉鍛樺彉鏇达級
+ if (result > 0 && updateVO.getAssignees() != null) {
+ // 鏌ヨ鐜版湁鐨勬墽琛屼汉鍛�
+ List<SysTaskAssignee> existingAssignees = sysTaskAssigneeMapper.selectSysTaskAssigneeByTaskId(updateVO.getTaskId());
+ List<Long> existingAssigneeIds = existingAssignees.stream()
+ .map(SysTaskAssignee::getUserId)
+ .collect(Collectors.toList());
+
+ List<Long> newAssigneeIds = updateVO.getAssignees().stream()
+ .map(TaskUpdateVO.AssigneeInfo::getUserId)
+ .collect(Collectors.toList());
+
+ // 姣旇緝鏂版棫鎵ц浜哄憳ID鍒楄〃锛屽垽鏂槸鍚︽湁鍙樺寲
+ boolean assigneesChanged = !new HashSet<>(existingAssigneeIds).equals(new HashSet<>(newAssigneeIds));
+
+ // 鍙湁鎵ц浜哄憳鍙戠敓鍙樺寲鏃舵墠鏇存柊
+ if (assigneesChanged) {
+ // 鍒犻櫎鏃х殑鎵ц浜哄憳鍏宠仈
+ sysTaskAssigneeMapper.deleteSysTaskAssigneeByTaskId(updateVO.getTaskId());
+
+ // 娣诲姞鏂扮殑鎵ц浜哄憳鍏宠仈
+ if (!updateVO.getAssignees().isEmpty()) {
+ // 灏� TaskUpdateVO.AssigneeInfo 杞崲涓� TaskCreateVO.AssigneeInfo
+ List<TaskCreateVO.AssigneeInfo> createAssignees = updateVO.getAssignees().stream()
+ .map(assignee -> {
+ TaskCreateVO.AssigneeInfo createAssignee = new TaskCreateVO.AssigneeInfo();
+ createAssignee.setUserId(assignee.getUserId());
+ createAssignee.setUserName(assignee.getUserName());
+ createAssignee.setUserType(assignee.getUserType());
+ return createAssignee;
+ })
+ .collect(Collectors.toList());
+ saveTaskAssignees(updateVO.getTaskId(), createAssignees, SecurityUtils.getUsername());
+ }
+
+ // 鏍囪闇�瑕侀噸鏂板悓姝ワ紙浜哄憳鍙樻洿锛�
+ needResync = true;
+ }
+ }
+
+ // 鏇存柊鎬ユ晳杞繍鎵╁睍淇℃伅锛堟娴嬪湴鍧�鍜屾垚浜や环鍙樻洿锛�
if (result > 0 && "EMERGENCY_TRANSFER".equals(oldTask.getTaskType()) && updateVO.getEmergencyInfo() != null) {
+ // 鑾峰彇鏃х殑鎬ユ晳杞繍淇℃伅
+ SysTaskEmergency oldEmergency = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(updateVO.getTaskId());
+
+ // 妫�娴嬭浆鍑哄尰闄㈠湴鍧�鍙樻洿
+ boolean hospitalOutAddressChanged = false;
+ if (updateVO.getEmergencyInfo().getHospitalOutAddress() != null
+ && oldEmergency != null
+ && !updateVO.getEmergencyInfo().getHospitalOutAddress().equals(oldEmergency.getHospitalOutAddress())) {
+ hospitalOutAddressChanged = true;
+ }
+
+ // 妫�娴嬭浆鍏ュ尰闄㈠湴鍧�鍙樻洿
+ boolean hospitalInAddressChanged = false;
+ if (updateVO.getEmergencyInfo().getHospitalInAddress() != null
+ && oldEmergency != null
+ && !updateVO.getEmergencyInfo().getHospitalInAddress().equals(oldEmergency.getHospitalInAddress())) {
+ hospitalInAddressChanged = true;
+ }
+
+ // 妫�娴嬫垚浜や环鍙樻洿
+ boolean transferPriceChanged = false;
+ if (updateVO.getEmergencyInfo().getTransferPrice() != null
+ && oldEmergency != null
+ && oldEmergency.getTransferPrice() != null
+ && updateVO.getEmergencyInfo().getTransferPrice().compareTo(oldEmergency.getTransferPrice()) != 0) {
+ transferPriceChanged = true;
+ }
+
+ // 鏇存柊鎬ユ晳杞繍淇℃伅
updateEmergencyInfo(updateVO.getTaskId(), updateVO);
+
+ // 濡傛灉鍦板潃鎴栨垚浜や环鍙戠敓鍙樻洿锛屾爣璁伴渶瑕侀噸鏂板悓姝�
+ if (hospitalOutAddressChanged || hospitalInAddressChanged || transferPriceChanged) {
+ needResync = true;
+ }
+ }
+
+ // 濡傛灉鏄�ユ晳杞繍浠诲姟涓旀湁鍙樻洿锛屾爣璁伴渶瑕侀噸鏂板悓姝�
+ if (result > 0 && "EMERGENCY_TRANSFER".equals(oldTask.getTaskType()) && needResync) {
+ try {
+ sysTaskEmergencyService.markNeedResync(updateVO.getTaskId());
+ } catch (Exception e) {
+ // 鏍囪澶辫触涓嶅奖鍝嶄富娴佺▼
+ }
}
// 璁板綍鎿嶄綔鏃ュ織
@@ -741,7 +952,7 @@
recordTaskLog(taskId, "UPDATE", "涓婁紶闄勪欢", null,
"涓婁紶鏂囦欢锛�" + file.getOriginalFilename() + "(鍒嗙被锛�" + categoryDesc + ")",
SecurityUtils.getUserId(), SecurityUtils.getUsername());
-
+
}
@@ -801,7 +1012,7 @@
recordTaskLog(taskId, "UPDATE", "涓婁紶闄勪欢", null,
"閫氳繃寰俊涓婁紶鏂囦欢锛�" + fileName + "(鍒嗙被锛�" + categoryDesc + ")",
SecurityUtils.getUserId(), SecurityUtils.getUsername());
-
+
}
@@ -1150,6 +1361,8 @@
task.setAttachments(attachments);
// 鏌ヨ鎿嶄綔鏃ュ織
task.setOperationLogs(sysTaskLogMapper.selectSysTaskLogByTaskId(taskId));
+ // 鏌ヨ鎵ц浜哄憳鍒楄〃
+ task.setAssignees(sysTaskAssigneeMapper.selectSysTaskAssigneeByTaskId(taskId));
// 鍔犺浇鎬ユ晳杞繍鎵╁睍淇℃伅
if ("EMERGENCY_TRANSFER".equals(task.getTaskType())) {
SysTaskEmergency emergencyInfo = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(taskId);
@@ -1250,6 +1463,30 @@
*/
private String generateTaskCode() {
return taskCodeGenerator.generateTaskCode();
+ }
+
+ /**
+ * 浠庡湴鍧�涓彁鍙栧煄甯傚悕绉帮紙鐢ㄤ簬鍦板浘鍦扮悊缂栫爜锛�
+ *
+ * @param address 鍦板潃
+ * @return 鍩庡競鍚嶇О
+ */
+ private String extractCityFromAddress(String address) {
+ if (address == null || address.trim().isEmpty()) {
+ return null;
+ }
+
+ // 甯歌鍩庡競鍚嶅垪琛�
+ String[] cities = {"骞垮窞", "娣卞湷", "涓滆帪", "浣涘北", "鐝犳捣", "鎯犲窞", "涓北", "姹熼棬", "婀涙睙", "鑲囧簡", "娓呰繙", "闊跺叧", "姊呭窞", "娌虫簮", "娼窞", "鎻槼", "姹曞ご", "姹曞熬", "浜戞诞", "闃虫睙","鍖椾含","涓婃捣","澶╂触"};
+
+
+ for (String city : cities) {
+ if (address.contains(city)) {
+ return city;
+ }
+ }
+
+ return null;
}
/**
@@ -1434,8 +1671,27 @@
emergencyInfo.setHospitalOutDepartmentId(createVO.getHospitalOut().getDepartmentId());
emergencyInfo.setHospitalOutBedNumber(createVO.getHospitalOut().getBedNumber());
emergencyInfo.setHospitalOutAddress(createVO.getHospitalOut().getAddress());
- emergencyInfo.setHospitalOutLongitude(createVO.getHospitalOut().getLongitude());
- emergencyInfo.setHospitalOutLatitude(createVO.getHospitalOut().getLatitude());
+
+ // GPS鍧愭爣锛氫紭鍏堜娇鐢ㄥ墠绔紶鍏ョ殑锛屽惁鍒欏悗绔嚜鍔ㄨ幏鍙�
+ if (createVO.getHospitalOut().getLongitude() != null && createVO.getHospitalOut().getLatitude() != null) {
+ emergencyInfo.setHospitalOutLongitude(createVO.getHospitalOut().getLongitude());
+ emergencyInfo.setHospitalOutLatitude(createVO.getHospitalOut().getLatitude());
+ } else if (mapService != null && createVO.getHospitalOut().getAddress() != null) {
+ // 鍚庣鑷姩鑾峰彇GPS鍧愭爣
+ try {
+ Map<String, Double> coords = mapService.geocoding(
+ createVO.getHospitalOut().getAddress(),
+ extractCityFromAddress(createVO.getHospitalOut().getAddress())
+ );
+ if (coords != null) {
+ emergencyInfo.setHospitalOutLongitude(BigDecimal.valueOf(coords.get("lng")));
+ emergencyInfo.setHospitalOutLatitude(BigDecimal.valueOf(coords.get("lat")));
+ log.info("杞嚭鍖婚櫌GPS鍧愭爣鑷姩鑾峰彇鎴愬姛: {}, {}", coords.get("lng"), coords.get("lat"));
+ }
+ } catch (Exception e) {
+ log.error("鑷姩鑾峰彇杞嚭鍖婚櫌GPS鍧愭爣澶辫触", e);
+ }
+ }
}
// 璁剧疆杞叆鍖婚櫌淇℃伅
@@ -1446,8 +1702,27 @@
emergencyInfo.setHospitalInDepartmentId(createVO.getHospitalIn().getDepartmentId());
emergencyInfo.setHospitalInBedNumber(createVO.getHospitalIn().getBedNumber());
emergencyInfo.setHospitalInAddress(createVO.getHospitalIn().getAddress());
- emergencyInfo.setHospitalInLongitude(createVO.getHospitalIn().getLongitude());
- emergencyInfo.setHospitalInLatitude(createVO.getHospitalIn().getLatitude());
+
+ // GPS鍧愭爣锛氫紭鍏堜娇鐢ㄥ墠绔紶鍏ョ殑锛屽惁鍒欏悗绔嚜鍔ㄨ幏鍙�
+ if (createVO.getHospitalIn().getLongitude() != null && createVO.getHospitalIn().getLatitude() != null) {
+ emergencyInfo.setHospitalInLongitude(createVO.getHospitalIn().getLongitude());
+ emergencyInfo.setHospitalInLatitude(createVO.getHospitalIn().getLatitude());
+ } else if (mapService != null && createVO.getHospitalIn().getAddress() != null) {
+ // 鍚庣鑷姩鑾峰彇GPS鍧愭爣
+ try {
+ Map<String, Double> coords = mapService.geocoding(
+ createVO.getHospitalIn().getAddress(),
+ extractCityFromAddress(createVO.getHospitalIn().getAddress())
+ );
+ if (coords != null) {
+ emergencyInfo.setHospitalInLongitude(BigDecimal.valueOf(coords.get("lng")));
+ emergencyInfo.setHospitalInLatitude(BigDecimal.valueOf(coords.get("lat")));
+ log.info("杞叆鍖婚櫌GPS鍧愭爣鑷姩鑾峰彇鎴愬姛: {}, {}", coords.get("lng"), coords.get("lat"));
+ }
+ } catch (Exception e) {
+ log.error("鑷姩鑾峰彇杞叆鍖婚櫌GPS鍧愭爣澶辫触", e);
+ }
+ }
}
// 璁剧疆璐圭敤淇℃伅
@@ -1479,8 +1754,6 @@
emergencyInfo.setDispatchSyncStatus(2);
emergencyInfo.setDispatchSyncTime(new Date());
emergencyInfo.setDispatchSyncErrorMsg("鏃х郴缁熷悓姝ヨ繃鏉�");
-
- // 绯荤粺瀛楁
}
// 绯荤粺瀛楁
emergencyInfo.setCreateTime(DateUtils.getNowDate());
@@ -1548,6 +1821,23 @@
}
if (emergencyInfo.getHospitalOutAddress() != null) {
existingInfo.setHospitalOutAddress(emergencyInfo.getHospitalOutAddress());
+
+ // 濡傛灉鏇存柊浜嗗湴鍧�浣嗘病鏈塆PS鍧愭爣锛屽悗绔嚜鍔ㄨ幏鍙�
+ if (emergencyInfo.getHospitalOutLongitude() == null && emergencyInfo.getHospitalOutLatitude() == null && mapService != null) {
+ try {
+ Map<String, Double> coords = mapService.geocoding(
+ emergencyInfo.getHospitalOutAddress(),
+ extractCityFromAddress(emergencyInfo.getHospitalOutAddress())
+ );
+ if (coords != null) {
+ existingInfo.setHospitalOutLongitude(BigDecimal.valueOf(coords.get("lng")));
+ existingInfo.setHospitalOutLatitude(BigDecimal.valueOf(coords.get("lat")));
+ log.info("杞嚭鍖婚櫌GPS鍧愭爣鑷姩鑾峰彇鎴愬姛: {}, {}", coords.get("lng"), coords.get("lat"));
+ }
+ } catch (Exception e) {
+ log.error("鑷姩鑾峰彇杞嚭鍖婚櫌GPS鍧愭爣澶辫触", e);
+ }
+ }
}
if (emergencyInfo.getHospitalOutLongitude() != null) {
existingInfo.setHospitalOutLongitude(emergencyInfo.getHospitalOutLongitude());
@@ -1574,6 +1864,23 @@
}
if (emergencyInfo.getHospitalInAddress() != null) {
existingInfo.setHospitalInAddress(emergencyInfo.getHospitalInAddress());
+
+ // 濡傛灉鏇存柊浜嗗湴鍧�浣嗘病鏈塆PS鍧愭爣锛屽悗绔嚜鍔ㄨ幏鍙�
+ if (emergencyInfo.getHospitalInLongitude() == null && emergencyInfo.getHospitalInLatitude() == null && mapService != null) {
+ try {
+ Map<String, Double> coords = mapService.geocoding(
+ emergencyInfo.getHospitalInAddress(),
+ extractCityFromAddress(emergencyInfo.getHospitalInAddress())
+ );
+ if (coords != null) {
+ existingInfo.setHospitalInLongitude(BigDecimal.valueOf(coords.get("lng")));
+ existingInfo.setHospitalInLatitude(BigDecimal.valueOf(coords.get("lat")));
+ log.info("杞叆鍖婚櫌GPS鍧愭爣鑷姩鑾峰彇鎴愬姛: {}, {}", coords.get("lng"), coords.get("lat"));
+ }
+ } catch (Exception e) {
+ log.error("鑷姩鑾峰彇杞叆鍖婚櫌GPS鍧愭爣澶辫触", e);
+ }
+ }
}
if (emergencyInfo.getHospitalInLongitude() != null) {
existingInfo.setHospitalInLongitude(emergencyInfo.getHospitalInLongitude());
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TiandituMapServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TiandituMapServiceImpl.java
new file mode 100644
index 0000000..996bf70
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TiandituMapServiceImpl.java
@@ -0,0 +1,94 @@
+package com.ruoyi.system.service.impl;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.common.utils.http.HttpUtils;
+import com.ruoyi.common.config.TiandituMapConfig;
+import com.ruoyi.system.service.IMapService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 澶╁湴鍥炬湇鍔″疄鐜�
+ *
+ * @author ruoyi
+ */
+@Service("tiandituMapService")
+public class TiandituMapServiceImpl implements IMapService {
+
+ private static final Logger logger = LoggerFactory.getLogger(TiandituMapServiceImpl.class);
+
+ @Autowired
+ private TiandituMapConfig tiandituMapConfig;
+
+ /**
+ * 鍦板潃杞珿PS鍧愭爣锛堝湴鐞嗙紪鐮侊級
+ *
+ * @param address 鍦板潃
+ * @param city 鍩庡競锛堝彲閫夛紝鐢ㄤ簬鎻愰珮瑙f瀽鍑嗙‘鎬э級
+ * @return GPS鍧愭爣锛屽寘鍚玪ng鍜宭at锛屽鏋滆幏鍙栧け璐ヨ繑鍥瀗ull
+ */
+ @Override
+ public Map<String, Double> geocoding(String address, String city) {
+ // 濡傛灉鍦板潃涓虹┖锛岀洿鎺ヨ繑鍥瀗ull
+ if (address == null || address.trim().isEmpty()) {
+ return null;
+ }
+
+ try {
+ // 鏋勫缓澶╁湴鍥惧湴鐞嗙紪鐮丄PI URL
+ String url = "http://api.tianditu.gov.cn/geocoder";
+ String params = "ds=" + URLEncoder.encode("{\"keyWord\":\"" + address + "\"}", StandardCharsets.UTF_8.toString()) +
+ "&tk=" + tiandituMapConfig.getTk();
+
+ logger.info("澶╁湴鍥惧湴鐞嗙紪鐮佽姹�: address={}, city={}", address, city);
+
+ // 鍙戦�丠TTP璇锋眰
+ String response = HttpUtils.sendGet(url, params);
+
+ // 瑙f瀽鍝嶅簲
+ JSONObject jsonObject = JSONObject.parseObject(response);
+
+ // 妫�鏌ョ姸鎬�
+ if (jsonObject.getInteger("status") == null || jsonObject.getInteger("status") != 0) {
+ logger.warn("澶╁湴鍥惧湴鐞嗙紪鐮佸け璐�: address={}, status={}, msg={}",
+ address, jsonObject.getInteger("status"), jsonObject.getString("msg"));
+ return null;
+ }
+
+ // 鎻愬彇鍧愭爣
+ JSONObject result = jsonObject.getJSONObject("result");
+ if (result == null) {
+ logger.warn("澶╁湴鍥惧湴鐞嗙紪鐮佸搷搴旀棤result: address={}", address);
+ return null;
+ }
+
+ JSONObject location = result.getJSONObject("location");
+ if (location == null) {
+ logger.warn("澶╁湴鍥惧湴鐞嗙紪鐮佸搷搴旀棤location: address={}", address);
+ return null;
+ }
+
+ double lng = location.getDouble("lon");
+ double lat = location.getDouble("lat");
+
+ logger.info("澶╁湴鍥惧湴鐞嗙紪鐮佹垚鍔�: address={}, lng={}, lat={}", address, lng, lat);
+
+ Map<String, Double> coordinates = new HashMap<>();
+ coordinates.put("lng", lng);
+ coordinates.put("lat", lat);
+
+ return coordinates;
+ } catch (Exception e) {
+ // 鎹曡幏鎵�鏈夊紓甯革紝閬垮厤褰卞搷涓绘祦绋�
+ logger.error("澶╁湴鍥惧湴鐞嗙紪鐮佸紓甯�: address=" + address, e);
+ return null;
+ }
+ }
+}
diff --git a/ruoyi-system/src/main/resources/mapper/system/DepartmentSyncMapper.xml b/ruoyi-system/src/main/resources/mapper/system/DepartmentSyncMapper.xml
index 65e76ae..d62ff57 100644
--- a/ruoyi-system/src/main/resources/mapper/system/DepartmentSyncMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/DepartmentSyncMapper.xml
@@ -9,8 +9,26 @@
<result property="departmentName" column="departmentName" />
<result property="parentId" column="parentID" />
<result property="parentName" column="parentName" />
+
</resultMap>
+ <select id="selectDepartAddress" resultType="java.util.HashMap">
+ <![CDATA[
+ select
+ ServiceBranch,
+ ServiceAddress,
+ ServiceAddress_lat,
+ ServiceAddress_lng,
+ UnitName,
+ UnitShort,
+ ServiceMinPrice,
+ ServiceUnitPrice,
+ ServiceLong
+ from IntroducerUnitData
+ where UnitState>0 and ServiceAddress_lat is not null and ServiceAddress_lng is not null and ServiceBranch<>''
+ ]]>
+ </select>
+
<!-- 鏌ヨ鍚堜綔鍗曚綅涓嬬殑鎵�鏈夊垎鍏徃 -->
<select id="selectBranchDepartments" resultMap="DepartmentSyncResult">
<![CDATA[
diff --git a/ruoyi-system/src/main/resources/mapper/system/PaidMoneyAddMapper.xml b/ruoyi-system/src/main/resources/mapper/system/PaidMoneyAddMapper.xml
new file mode 100644
index 0000000..a9ebe04
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/PaidMoneyAddMapper.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.PaidMoneyAddMapper">
+
+ <resultMap id="PaidMoneyAddResult" type="com.ruoyi.system.domain.PaidMoneyAdd">
+ <result property="id" column="id" />
+ <result property="toServiceOrdID" column="ToServiceOrdID" />
+ <result property="toDispatchOrdID" column="ToDispatchOrdID" />
+ <result property="addMoneyType" column="AddMoneyType" />
+ <result property="addMoney" column="AddMoney" />
+ <result property="addMoneyExplain" column="AddMoneyExplain" />
+ <result property="addMoneyTime" column="AddMoneyTime" />
+ <result property="addMoneyOAID" column="AddMoneyOAID" />
+ </resultMap>
+
+ <sql id="selectPaidMoneyAddVo">
+ SELECT id, ToServiceOrdID, ToDispatchOrdID, AddMoneyType, AddMoney,
+ AddMoneyExplain, AddMoneyTime, AddMoneyOAID
+ FROM PaidMoney_Add
+ </sql>
+
+ <select id="selectById" parameterType="Long" resultMap="PaidMoneyAddResult">
+ <include refid="selectPaidMoneyAddVo"/>
+ WHERE id = #{id}
+ </select>
+
+ <select id="selectByOrderIds" resultMap="PaidMoneyAddResult">
+ <include refid="selectPaidMoneyAddVo"/>
+ WHERE ToServiceOrdID = #{toServiceOrdID}
+ AND ToDispatchOrdID = #{toDispatchOrdID}
+ ORDER BY id DESC
+ </select>
+
+ <select id="selectRecentRecords" resultMap="PaidMoneyAddResult">
+ <include refid="selectPaidMoneyAddVo"/>
+ WHERE AddMoneyTime >= DATEADD(HOUR, -#{hours}, GETDATE())
+ ORDER BY id DESC
+ </select>
+
+ <insert id="insert" parameterType="com.ruoyi.system.domain.PaidMoneyAdd" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO PaidMoney_Add
+ <trim prefix="(" suffix=")" suffixOverrides=",">
+ <if test="toServiceOrdID != null">ToServiceOrdID,</if>
+ <if test="toDispatchOrdID != null">ToDispatchOrdID,</if>
+ <if test="addMoneyType != null">AddMoneyType,</if>
+ <if test="addMoney != null">AddMoney,</if>
+ <if test="addMoneyExplain != null and addMoneyExplain != ''">AddMoneyExplain,</if>
+ <if test="addMoneyTime != null">AddMoneyTime,</if>
+ <if test="addMoneyOAID != null">AddMoneyOAID,</if>
+ </trim>
+ <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
+ <if test="toServiceOrdID != null">#{toServiceOrdID},</if>
+ <if test="toDispatchOrdID != null">#{toDispatchOrdID},</if>
+ <if test="addMoneyType != null">#{addMoneyType},</if>
+ <if test="addMoney != null">#{addMoney},</if>
+ <if test="addMoneyExplain != null and addMoneyExplain != ''">#{addMoneyExplain},</if>
+ <if test="addMoneyTime != null">#{addMoneyTime},</if>
+ <if test="addMoneyOAID != null">#{addMoneyOAID},</if>
+ </trim>
+ </insert>
+
+ <update id="update" parameterType="com.ruoyi.system.domain.PaidMoneyAdd">
+ UPDATE PaidMoney_Add
+ <trim prefix="SET" suffixOverrides=",">
+ <if test="toServiceOrdID != null">ToServiceOrdID = #{toServiceOrdID},</if>
+ <if test="toDispatchOrdID != null">ToDispatchOrdID = #{toDispatchOrdID},</if>
+ <if test="addMoneyType != null">AddMoneyType = #{addMoneyType},</if>
+ <if test="addMoney != null">AddMoney = #{addMoney},</if>
+ <if test="addMoneyExplain != null">AddMoneyExplain = #{addMoneyExplain},</if>
+ <if test="addMoneyTime != null">AddMoneyTime = #{addMoneyTime},</if>
+ <if test="addMoneyOAID != null">AddMoneyOAID = #{addMoneyOAID},</if>
+ </trim>
+ WHERE id = #{id}
+ </update>
+
+ <delete id="deleteById" parameterType="Long">
+ DELETE FROM PaidMoney_Add WHERE id = #{id}
+ </delete>
+
+</mapper>
diff --git a/ruoyi-system/src/main/resources/mapper/system/PaidMoneyMapper.xml b/ruoyi-system/src/main/resources/mapper/system/PaidMoneyMapper.xml
new file mode 100644
index 0000000..f847997
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/PaidMoneyMapper.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.PaidMoneyMapper">
+
+ <resultMap type="com.ruoyi.system.domain.PaidMoney" id="PaidMoneyResult">
+ <result property="id" column="id" />
+ <result property="paidMoneyClass" column="PaidMoneyClass" />
+ <result property="serviceOrdIDDt" column="ServiceOrdIDDt" />
+ <result property="dispatchOrdIDDt" column="DispatchOrdIDDt" />
+ <result property="paidMoney" column="PaidMoney" />
+ <result property="paidMoneyType" column="PaidMoneyType" />
+ <result property="paidMoneyMono" column="PaidMoneyMono" />
+ <result property="paidMoneyTime" column="PaidMoneyTime" />
+ <result property="paidMoneyOaID" column="PaidMoneyOaID" />
+ <result property="paidMoneyUnitID" column="PaidMoneyUnitID" />
+ <result property="paidMoneyAPID" column="PaidMoney_AP_ID" />
+ <result property="paidMoneyAPTime" column="PaidMoney_AP_Time" />
+ <result property="paidMoneyAPCheck" column="PaidMoney_AP_Check" />
+ <result property="paidMoneyTimestamp" column="PaidMoneyTimestamp" />
+ </resultMap>
+
+ <sql id="selectPaidMoneyVo">
+ select id, PaidMoneyClass, ServiceOrdIDDt, DispatchOrdIDDt, PaidMoney, PaidMoneyType,
+ PaidMoneyMono, PaidMoneyTime, PaidMoneyOaID, PaidMoneyUnitID, PaidMoney_AP_ID,
+ PaidMoney_AP_Time, PaidMoney_AP_Check, PaidMoneyTimestamp
+ from PaidMoney
+ </sql>
+
+ <select id="selectById" parameterType="Long" resultMap="PaidMoneyResult">
+ <include refid="selectPaidMoneyVo"/>
+ where id = #{id}
+ </select>
+
+ <select id="selectByOrderIds" resultMap="PaidMoneyResult">
+ <include refid="selectPaidMoneyVo"/>
+ where ServiceOrdIDDt = #{serviceOrdIDDt}
+ <if test="dispatchOrdIDDt != null">
+ and DispatchOrdIDDt = #{dispatchOrdIDDt}
+ </if>
+ order by PaidMoneyTime desc
+ </select>
+
+ <insert id="insert" parameterType="com.ruoyi.system.domain.PaidMoney" useGeneratedKeys="true" keyProperty="id">
+ insert into PaidMoney
+ <trim prefix="(" suffix=")" suffixOverrides=",">
+ <if test="paidMoneyClass != null and paidMoneyClass != ''">PaidMoneyClass,</if>
+ <if test="serviceOrdIDDt != null">ServiceOrdIDDt,</if>
+ <if test="dispatchOrdIDDt != null">DispatchOrdIDDt,</if>
+ <if test="paidMoney != null">PaidMoney,</if>
+ <if test="paidMoneyType != null">PaidMoneyType,</if>
+ <if test="paidMoneyMono != null and paidMoneyMono != ''">PaidMoneyMono,</if>
+ <if test="paidMoneyTime != null">PaidMoneyTime,</if>
+ <if test="paidMoneyOaID != null">PaidMoneyOaID,</if>
+ <if test="paidMoneyUnitID != null">PaidMoneyUnitID,</if>
+ <if test="paidMoneyAPID != null">PaidMoney_AP_ID,</if>
+ <if test="paidMoneyAPTime != null">PaidMoney_AP_Time,</if>
+ <if test="paidMoneyAPCheck != null">PaidMoney_AP_Check,</if>
+ <if test="paidMoneyTimestamp != null and paidMoneyTimestamp != ''">PaidMoneyTimestamp,</if>
+ </trim>
+ <trim prefix="values (" suffix=")" suffixOverrides=",">
+ <if test="paidMoneyClass != null and paidMoneyClass != ''">#{paidMoneyClass},</if>
+ <if test="serviceOrdIDDt != null">#{serviceOrdIDDt},</if>
+ <if test="dispatchOrdIDDt != null">#{dispatchOrdIDDt},</if>
+ <if test="paidMoney != null">#{paidMoney},</if>
+ <if test="paidMoneyType != null">#{paidMoneyType},</if>
+ <if test="paidMoneyMono != null and paidMoneyMono != ''">#{paidMoneyMono},</if>
+ <if test="paidMoneyTime != null">#{paidMoneyTime},</if>
+ <if test="paidMoneyOaID != null">#{paidMoneyOaID},</if>
+ <if test="paidMoneyUnitID != null">#{paidMoneyUnitID},</if>
+ <if test="paidMoneyAPID != null">#{paidMoneyAPID},</if>
+ <if test="paidMoneyAPTime != null">#{paidMoneyAPTime},</if>
+ <if test="paidMoneyAPCheck != null">#{paidMoneyAPCheck},</if>
+ <if test="paidMoneyTimestamp != null and paidMoneyTimestamp != ''">#{paidMoneyTimestamp},</if>
+ </trim>
+ </insert>
+
+ <update id="update" parameterType="com.ruoyi.system.domain.PaidMoney">
+ update PaidMoney
+ <trim prefix="SET" suffixOverrides=",">
+ <if test="paidMoneyClass != null and paidMoneyClass != ''">PaidMoneyClass = #{paidMoneyClass},</if>
+ <if test="serviceOrdIDDt != null">ServiceOrdIDDt = #{serviceOrdIDDt},</if>
+ <if test="dispatchOrdIDDt != null">DispatchOrdIDDt = #{dispatchOrdIDDt},</if>
+ <if test="paidMoney != null">PaidMoney = #{paidMoney},</if>
+ <if test="paidMoneyType != null">PaidMoneyType = #{paidMoneyType},</if>
+ <if test="paidMoneyMono != null and paidMoneyMono != ''">PaidMoneyMono = #{paidMoneyMono},</if>
+ <if test="paidMoneyTime != null">PaidMoneyTime = #{paidMoneyTime},</if>
+ <if test="paidMoneyOaID != null">PaidMoneyOaID = #{paidMoneyOaID},</if>
+ <if test="paidMoneyUnitID != null">PaidMoneyUnitID = #{paidMoneyUnitID},</if>
+ <if test="paidMoneyAPID != null">PaidMoney_AP_ID = #{paidMoneyAPID},</if>
+ <if test="paidMoneyAPTime != null">PaidMoney_AP_Time = #{paidMoneyAPTime},</if>
+ <if test="paidMoneyAPCheck != null">PaidMoney_AP_Check = #{paidMoneyAPCheck},</if>
+ <if test="paidMoneyTimestamp != null and paidMoneyTimestamp != ''">PaidMoneyTimestamp = #{paidMoneyTimestamp},</if>
+ </trim>
+ where id = #{id}
+ </update>
+
+ <delete id="deleteById" parameterType="Long">
+ delete from PaidMoney where id = #{id}
+ </delete>
+
+ <select id="selectRecentRecords" resultMap="PaidMoneyResult">
+ <include refid="selectPaidMoneyVo"/>
+ where PaidMoneyTime >= DATEADD(DAY, -#{days}, GETDATE())
+ order by PaidMoneyTime desc
+ </select>
+
+</mapper>
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml
index 9b9bce3..67c9fae 100644
--- a/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml
@@ -19,6 +19,9 @@
<result property="departmentId" column="department_id" />
<result property="serviceOrderClass" column="service_order_class" />
<result property="dispatchOrderClass" column="dispatch_order_class" />
+ <result property="departureAddress" column="departure_address" />
+ <result property="departureLongitude" column="departure_longitude" />
+ <result property="departureLatitude" column="departure_latitude" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
@@ -26,7 +29,7 @@
</resultMap>
<sql id="selectDeptVo">
- select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.department_id, d.service_order_class, d.dispatch_order_class, d.create_by, d.create_time
+ select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.department_id, d.service_order_class, d.dispatch_order_class, d.departure_address, d.departure_longitude, d.departure_latitude, d.create_by, d.create_time
from sys_dept d
</sql>
@@ -54,6 +57,14 @@
order by d.parent_id, d.order_num
</select>
+
+ <select id="selectDeptListByParentId" parameterType="Long" resultMap="SysDeptResult">
+ <include refid="selectDeptVo"/>
+ where d.del_flag = '0'
+
+ AND parent_id = #{parentId}
+
+ </select>
<select id="selectDeptListByRoleId" resultType="Long">
select d.dept_id
@@ -67,7 +78,7 @@
</select>
<select id="selectDeptById" parameterType="Long" resultMap="SysDeptResult">
- select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.department_id, d.service_order_class, d.dispatch_order_class,
+ select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.department_id, d.service_order_class, d.dispatch_order_class, d.departure_address, d.departure_longitude, d.departure_latitude,
(select dept_name from sys_dept where dept_id = d.parent_id) parent_name
from sys_dept d
where d.dept_id = #{deptId}
@@ -109,6 +120,9 @@
<if test="departmentId != null">department_id,</if>
<if test="serviceOrderClass != null and serviceOrderClass != ''">service_order_class,</if>
<if test="dispatchOrderClass != null and dispatchOrderClass != ''">dispatch_order_class,</if>
+ <if test="departureAddress != null and departureAddress != ''">departure_address,</if>
+ <if test="departureLongitude != null">departure_longitude,</if>
+ <if test="departureLatitude != null">departure_latitude,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
create_time
)values(
@@ -124,6 +138,9 @@
<if test="departmentId != null">#{departmentId},</if>
<if test="serviceOrderClass != null and serviceOrderClass != ''">#{serviceOrderClass},</if>
<if test="dispatchOrderClass != null and dispatchOrderClass != ''">#{dispatchOrderClass},</if>
+ <if test="departureAddress != null and departureAddress != ''">#{departureAddress},</if>
+ <if test="departureLongitude != null">#{departureLongitude},</if>
+ <if test="departureLatitude != null">#{departureLatitude},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
sysdate()
)
@@ -143,6 +160,9 @@
<if test="departmentId != null">department_id = #{departmentId},</if>
<if test="serviceOrderClass != null">service_order_class = #{serviceOrderClass},</if>
<if test="dispatchOrderClass != null">dispatch_order_class = #{dispatchOrderClass},</if>
+ <if test="departureAddress != null">departure_address = #{departureAddress},</if>
+ <if test="departureLongitude != null">departure_longitude = #{departureLongitude},</if>
+ <if test="departureLatitude != null">departure_latitude = #{departureLatitude},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = sysdate()
</set>
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysTaskAdditionalFeeMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysTaskAdditionalFeeMapper.xml
new file mode 100644
index 0000000..8afa1e2
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysTaskAdditionalFeeMapper.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysTaskAdditionalFeeMapper">
+
+ <resultMap type="SysTaskAdditionalFee" id="SysTaskAdditionalFeeResult">
+ <result property="id" column="id" />
+ <result property="taskId" column="task_id" />
+ <result property="feeType" column="fee_type" />
+ <result property="feeName" column="fee_name" />
+ <result property="unitAmount" column="unit_amount" />
+ <result property="quantity" column="quantity" />
+ <result property="totalAmount" column="total_amount" />
+ <result property="remark" column="remark" />
+ <result property="createdBy" column="created_by" />
+ <result property="createdTime" column="created_time" />
+ <result property="pid" column="pid" />
+ <result property="syncStatus" column="sync_status" />
+ <result property="syncTime" column="sync_time" />
+ </resultMap>
+
+ <sql id="selectSysTaskAdditionalFeeVo">
+ select id, task_id, fee_type, fee_name, unit_amount, quantity, total_amount, remark, created_by, created_time, pid, sync_status, sync_time
+ from sys_task_additional_fee
+ </sql>
+
+ <select id="selectByTaskId" parameterType="Long" resultMap="SysTaskAdditionalFeeResult">
+ <include refid="selectSysTaskAdditionalFeeVo"/>
+ where task_id = #{taskId}
+ order by created_time desc
+ </select>
+
+ <insert id="insert" parameterType="SysTaskAdditionalFee" useGeneratedKeys="true" keyProperty="id">
+ insert into sys_task_additional_fee
+ <trim prefix="(" suffix=")" suffixOverrides=",">
+ <if test="taskId != null">task_id,</if>
+ <if test="feeType != null and feeType != ''">fee_type,</if>
+ <if test="feeName != null and feeName != ''">fee_name,</if>
+ <if test="unitAmount != null">unit_amount,</if>
+ <if test="quantity != null">quantity,</if>
+ <if test="totalAmount != null">total_amount,</if>
+ <if test="remark != null">remark,</if>
+ <if test="createdBy != null and createdBy != ''">created_by,</if>
+ </trim>
+ <trim prefix="values (" suffix=")" suffixOverrides=",">
+ <if test="taskId != null">#{taskId},</if>
+ <if test="feeType != null and feeType != ''">#{feeType},</if>
+ <if test="feeName != null and feeName != ''">#{feeName},</if>
+ <if test="unitAmount != null">#{unitAmount},</if>
+ <if test="quantity != null">#{quantity},</if>
+ <if test="totalAmount != null">#{totalAmount},</if>
+ <if test="remark != null">#{remark},</if>
+ <if test="createdBy != null and createdBy != ''">#{createdBy},</if>
+ </trim>
+ </insert>
+
+ <delete id="deleteById" parameterType="Long">
+ delete from sys_task_additional_fee where id = #{id}
+ </delete>
+
+ <delete id="deleteByTaskId" parameterType="Long">
+ delete from sys_task_additional_fee where task_id = #{taskId}
+ </delete>
+
+ <select id="selectByPid" parameterType="Long" resultMap="SysTaskAdditionalFeeResult">
+ <include refid="selectSysTaskAdditionalFeeVo"/>
+ where pid = #{pid}
+ </select>
+
+ <update id="updateSyncInfo">
+ update sys_task_additional_fee
+ set pid = #{pid},
+ sync_status = #{syncStatus},
+ sync_time = #{syncTime}
+ where id = #{id}
+ </update>
+
+ <select id="selectUnsyncedFees" resultMap="SysTaskAdditionalFeeResult">
+ <include refid="selectSysTaskAdditionalFeeVo"/>
+ where (sync_status = 0 or sync_status = 3 or sync_status is null)
+ order by id desc
+ limit 100
+ </select>
+
+</mapper>
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysTaskEmergencyMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysTaskEmergencyMapper.xml
index 3318e20..05675ce 100644
--- a/ruoyi-system/src/main/resources/mapper/system/SysTaskEmergencyMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/SysTaskEmergencyMapper.xml
@@ -44,6 +44,7 @@
<result property="dispatchSyncStatus" column="dispatch_sync_status" />
<result property="dispatchSyncTime" column="dispatch_sync_time" />
<result property="dispatchSyncErrorMsg" column="dispatch_sync_error_msg" />
+ <result property="needResync" column="need_resync" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<result property="createBy" column="create_by" />
@@ -58,7 +59,7 @@
hospital_in_department_id, hospital_in_bed_number, hospital_in_address, hospital_in_longitude,
hospital_in_latitude, transfer_distance, transfer_price, passenger_contact,
passenger_phone, disease_ids, document_type_id, task_type_id, legacy_service_ord_id, legacy_dispatch_ord_id,
- sync_status, sync_time, sync_error_msg, dispatch_sync_status, dispatch_sync_time, dispatch_sync_error_msg,
+ sync_status, sync_time, sync_error_msg, dispatch_sync_status, dispatch_sync_time, dispatch_sync_error_msg, need_resync,
create_time, update_time, create_by, update_by
from sys_task_emergency
</sql>
@@ -114,6 +115,7 @@
<if test="dispatchSyncStatus != null">dispatch_sync_status,</if>
<if test="dispatchSyncTime != null">dispatch_sync_time,</if>
<if test="dispatchSyncErrorMsg != null">dispatch_sync_error_msg,</if>
+ <if test="needResync != null">need_resync,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
<if test="createBy != null">create_by,</if>
@@ -158,6 +160,7 @@
<if test="dispatchSyncStatus != null">#{dispatchSyncStatus},</if>
<if test="dispatchSyncTime != null">#{dispatchSyncTime},</if>
<if test="dispatchSyncErrorMsg != null">#{dispatchSyncErrorMsg},</if>
+ <if test="needResync != null">#{needResync},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="createBy != null">#{createBy},</if>
@@ -205,6 +208,7 @@
<if test="dispatchSyncStatus != null">dispatch_sync_status = #{dispatchSyncStatus},</if>
<if test="dispatchSyncTime != null">dispatch_sync_time = #{dispatchSyncTime},</if>
<if test="dispatchSyncErrorMsg != null">dispatch_sync_error_msg = #{dispatchSyncErrorMsg},</if>
+ <if test="needResync != null">need_resync = #{needResync},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
</trim>
@@ -297,5 +301,29 @@
<include refid="selectSysTaskEmergencyVo"/>
where legacy_dispatch_ord_id = #{legacyDispatchOrdId}
</select>
+
+ <!-- 鏌ヨ闇�瑕侀噸鏂板悓姝ョ殑浠诲姟锛堣溅杈嗘垨浜哄憳鍙樻洿锛� -->
+ <select id="selectNeedResyncTasks" resultMap="SysTaskEmergencyResult">
+ <include refid="selectSysTaskEmergencyVo"/>
+ where need_resync = 1
+ and dispatch_sync_status = 2
+ and legacy_dispatch_ord_id is not null
+ and task_id in (
+ select task_id from sys_task
+ where task_type = 'EMERGENCY_TRANSFER'
+ and task_status not in ('COMPLETED', 'CANCELLED')
+ and del_flag = '0'
+ )
+ order by id asc
+ <if test="offset != null and limit != null">
+ limit #{offset}, #{limit}
+ </if>
+ <if test="offset == null and limit != null">
+ limit #{limit}
+ </if>
+ <if test="offset == null and limit == null">
+ limit 100
+ </if>
+ </select>
</mapper>
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysTaskPaymentMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysTaskPaymentMapper.xml
new file mode 100644
index 0000000..f58b3ec
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysTaskPaymentMapper.xml
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysTaskPaymentMapper">
+
+ <resultMap type="SysTaskPayment" id="SysTaskPaymentResult">
+ <result property="id" column="id" />
+ <result property="taskId" column="task_id" />
+ <result property="totalAmount" column="total_amount" />
+ <result property="settlementAmount" column="settlement_amount" />
+ <result property="paymentMethod" column="payment_method" />
+ <result property="payStatus" column="pay_status" />
+ <result property="payTime" column="pay_time" />
+ <result property="outTradeNo" column="out_trade_no" />
+ <result property="tradeNo" column="trade_no" />
+ <result property="codeUrl" column="code_url" />
+ <result property="qrExpireTime" column="qr_expire_time" />
+ <result property="provider" column="provider" />
+ <result property="paymentRefId" column="payment_ref_id" />
+ <result property="callbackUrl" column="callback_url" />
+ <result property="remark" column="remark" />
+ <result property="createdBy" column="created_by" />
+ <result property="createdTime" column="created_time" />
+ <result property="updateTime" column="update_time" />
+ <result property="pid" column="pid" />
+ <result property="syncStatus" column="sync_status" />
+ <result property="syncTime" column="sync_time" />
+ </resultMap>
+
+ <sql id="selectSysTaskPaymentVo">
+ select id, task_id, total_amount, settlement_amount, payment_method, pay_status, pay_time,
+ out_trade_no, trade_no, code_url, qr_expire_time, provider, payment_ref_id, callback_url,
+ remark, created_by, created_time, update_time, pid, sync_status, sync_time
+ from sys_task_payment
+ </sql>
+
+ <select id="selectLatestPaidByTaskId" parameterType="Long" resultMap="SysTaskPaymentResult">
+ <include refid="selectSysTaskPaymentVo"/>
+ where task_id = #{taskId} and pay_status = 'PAID'
+ order by pay_time desc
+ limit 1
+ </select>
+
+ <select id="selectAllPaidByTaskId" parameterType="Long" resultMap="SysTaskPaymentResult">
+ <include refid="selectSysTaskPaymentVo"/>
+ where task_id = #{taskId} and pay_status = 'PAID'
+ order by pay_time desc
+ </select>
+
+ <select id="selectByTaskId" parameterType="Long" resultMap="SysTaskPaymentResult">
+ <include refid="selectSysTaskPaymentVo"/>
+ where task_id = #{taskId}
+ order by created_time desc
+ </select>
+
+ <select id="selectById" parameterType="Long" resultMap="SysTaskPaymentResult">
+ <include refid="selectSysTaskPaymentVo"/>
+ where id = #{id}
+ </select>
+
+ <select id="selectByOutTradeNo" parameterType="String" resultMap="SysTaskPaymentResult">
+ <include refid="selectSysTaskPaymentVo"/>
+ where out_trade_no = #{outTradeNo}
+ </select>
+
+ <insert id="insert" parameterType="SysTaskPayment" useGeneratedKeys="true" keyProperty="id">
+ insert into sys_task_payment
+ <trim prefix="(" suffix=")" suffixOverrides=",">
+ <if test="taskId != null">task_id,</if>
+ <if test="totalAmount != null">total_amount,</if>
+ <if test="settlementAmount != null">settlement_amount,</if>
+ <if test="paymentMethod != null and paymentMethod != ''">payment_method,</if>
+ <if test="payStatus != null and payStatus != ''">pay_status,</if>
+ <if test="payTime != null">pay_time,</if>
+ <if test="outTradeNo != null and outTradeNo != ''">out_trade_no,</if>
+ <if test="tradeNo != null and tradeNo != ''">trade_no,</if>
+ <if test="codeUrl != null and codeUrl != ''">code_url,</if>
+ <if test="qrExpireTime != null">qr_expire_time,</if>
+ <if test="provider != null and provider != ''">provider,</if>
+ <if test="paymentRefId != null and paymentRefId != ''">payment_ref_id,</if>
+ <if test="callbackUrl != null and callbackUrl != ''">callback_url,</if>
+ <if test="remark != null">remark,</if>
+ <if test="createdBy != null and createdBy != ''">created_by,</if>
+ <if test="createdTime != null">created_time,</if>
+ <if test="pid != null">pid,</if>
+ <if test="syncStatus != null">sync_status,</if>
+ <if test="syncTime != null">sync_time,</if>
+ </trim>
+ <trim prefix="values (" suffix=")" suffixOverrides=",">
+ <if test="taskId != null">#{taskId},</if>
+ <if test="totalAmount != null">#{totalAmount},</if>
+ <if test="settlementAmount != null">#{settlementAmount},</if>
+ <if test="paymentMethod != null and paymentMethod != ''">#{paymentMethod},</if>
+ <if test="payStatus != null and payStatus != ''">#{payStatus},</if>
+ <if test="payTime != null">#{payTime},</if>
+ <if test="outTradeNo != null and outTradeNo != ''">#{outTradeNo},</if>
+ <if test="tradeNo != null and tradeNo != ''">#{tradeNo},</if>
+ <if test="codeUrl != null and codeUrl != ''">#{codeUrl},</if>
+ <if test="qrExpireTime != null">#{qrExpireTime},</if>
+ <if test="provider != null and provider != ''">#{provider},</if>
+ <if test="paymentRefId != null and paymentRefId != ''">#{paymentRefId},</if>
+ <if test="callbackUrl != null and callbackUrl != ''">#{callbackUrl},</if>
+ <if test="remark != null">#{remark},</if>
+ <if test="createdBy != null and createdBy != ''">#{createdBy},</if>
+ <if test="createdTime != null">#{createdTime},</if>
+ <if test="pid != null">#{pid},</if>
+ <if test="syncStatus != null">#{syncStatus},</if>
+ <if test="syncTime != null">#{syncTime},</if>
+ </trim>
+ </insert>
+
+ <update id="update" parameterType="SysTaskPayment">
+ update sys_task_payment
+ <trim prefix="SET" suffixOverrides=",">
+ <if test="payStatus != null and payStatus != ''">pay_status = #{payStatus},</if>
+ <if test="payTime != null">pay_time = #{payTime},</if>
+ <if test="tradeNo != null and tradeNo != ''">trade_no = #{tradeNo},</if>
+ <if test="remark != null">remark = #{remark},</if>
+ update_time = now(),
+ </trim>
+ where id = #{id}
+ </update>
+
+ <update id="updatePayStatus">
+ update sys_task_payment
+ set pay_status = #{payStatus},
+ <if test="tradeNo != null and tradeNo != ''">trade_no = #{tradeNo},</if>
+ pay_time = now(),
+ update_time = now()
+ where id = #{id}
+ </update>
+
+ <select id="selectByPid" parameterType="Long" resultMap="SysTaskPaymentResult">
+ <include refid="selectSysTaskPaymentVo"/>
+ where pid = #{pid}
+ </select>
+
+ <update id="updateSyncInfo">
+ update sys_task_payment
+ set pid = #{pid},
+ sync_status = #{syncStatus},
+ sync_time = #{syncTime},
+ update_time = now()
+ where id = #{id}
+ </update>
+
+ <select id="selectUnsyncedPaidPayments" resultMap="SysTaskPaymentResult">
+ <include refid="selectSysTaskPaymentVo"/>
+ where pay_status = 'PAID'
+ and (sync_status = 0 or sync_status = 3 or sync_status is null)
+ order by id desc
+ limit 100
+ </select>
+
+</mapper>
diff --git a/ruoyi-ui/src/api/task.js b/ruoyi-ui/src/api/task.js
index f269b23..ce09668 100644
--- a/ruoyi-ui/src/api/task.js
+++ b/ruoyi-ui/src/api/task.js
@@ -1,5 +1,7 @@
import request from '@/utils/request'
+// ========== 浠诲姟绠$悊鐩稿叧API ==========
+
// 鏌ヨ浠诲姟绠$悊鍒楄〃 (鍚庡彴绠$悊绔�)
export function listTask(query) {
return request({
@@ -244,4 +246,33 @@
method: 'put',
params: { status }
})
+}
+
+// ========== 浠诲姟鏀粯鐩稿叧API ==========
+
+// 鑾峰彇浠诲姟鏀粯淇℃伅
+export function getPaymentInfo(taskId) {
+ return request({
+ url: '/task/payment/info',
+ method: 'get',
+ params: { taskId }
+ })
+}
+
+// 鏌ヨ浠诲姟鐨勯檮鍔犺垂鐢ㄥ垪琛�
+export function getAdditionalFees(taskId) {
+ return request({
+ url: '/task/payment/info',
+ method: 'get',
+ params: { taskId }
+ })
+}
+
+// 鏌ヨ浠诲姟鐨勬渶鏂版敮浠樿褰�
+export function getLatestPayment(taskId) {
+ return request({
+ url: '/task/payment/info',
+ method: 'get',
+ params: { taskId }
+ })
}
\ No newline at end of file
diff --git a/ruoyi-ui/src/views/task/detail/index.vue b/ruoyi-ui/src/views/task/detail/index.vue
index 90d608e..801568c 100644
--- a/ruoyi-ui/src/views/task/detail/index.vue
+++ b/ruoyi-ui/src/views/task/detail/index.vue
@@ -28,12 +28,62 @@
<!-- GPS閲岀▼缁熻缁勪欢 -->
<task-mileage-detail v-if="taskInfo.taskId" :task-id="taskInfo.taskId" />
+
+ <!-- 鏀粯淇℃伅鍗$墖 -->
+ <el-card v-if="showPaymentInfo && paymentInfo" class="box-card" style="margin-top: 20px;" shadow="hover">
+ <div slot="header" class="clearfix">
+ <span><i class="el-icon-wallet"></i> 鏀粯淇℃伅</span>
+ </div>
+
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="鎴愪氦浠�">楼{{ paymentInfo.transferPrice || 0 }}</el-descriptions-item>
+ <el-descriptions-item label="闄勫姞璐圭敤">楼{{ paymentInfo.additionalAmount || 0 }}</el-descriptions-item>
+ <el-descriptions-item label="鎬婚噾棰�">
+ <span style="color: #e54d42; font-weight: bold; font-size: 16px;">楼{{ paymentInfo.totalAmount || 0 }}</span>
+ </el-descriptions-item>
+ <el-descriptions-item label="鏀粯鐘舵��" v-if="paymentInfo.latestPayment">
+ <el-tag :type="getPaymentStatusType(paymentInfo.latestPayment.payStatus)">
+ {{ getPaymentStatusText(paymentInfo.latestPayment.payStatus) }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="鏀粯鐘舵��" v-else>
+ <el-tag type="info">鏈敮浠�</el-tag>
+ </el-descriptions-item>
+ <template v-if="paymentInfo.latestPayment">
+ <el-descriptions-item label="鏀粯鏂瑰紡">{{ getPaymentMethodText(paymentInfo.latestPayment.paymentMethod) }}</el-descriptions-item>
+ <el-descriptions-item label="缁撶畻閲戦">楼{{ paymentInfo.latestPayment.settlementAmount }}</el-descriptions-item>
+ <el-descriptions-item label="浜ゆ槗鍙�" v-if="paymentInfo.latestPayment.tradeNo">{{ paymentInfo.latestPayment.tradeNo }}</el-descriptions-item>
+ <el-descriptions-item label="鏀粯鏃堕棿" v-if="paymentInfo.latestPayment.payTime">{{ paymentInfo.latestPayment.payTime }}</el-descriptions-item>
+ </template>
+ </el-descriptions>
+
+ <!-- 闄勫姞璐圭敤鏄庣粏 -->
+ <div v-if="paymentInfo.additionalFees && paymentInfo.additionalFees.length > 0" style="margin-top: 20px;">
+ <el-divider content-position="left">闄勫姞璐圭敤鏄庣粏</el-divider>
+ <el-table :data="paymentInfo.additionalFees" border style="width: 100%">
+ <el-table-column prop="feeName" label="璐圭敤鍚嶇О" width="150" />
+ <el-table-column prop="unitAmount" label="鍗曚环" width="100">
+ <template slot-scope="scope">
+ 楼{{ scope.row.unitAmount }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="quantity" label="鏁伴噺" width="80" />
+ <el-table-column prop="totalAmount" label="灏忚" width="100">
+ <template slot-scope="scope">
+ 楼{{ scope.row.totalAmount }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="remark" label="澶囨敞" show-overflow-tooltip />
+ </el-table>
+ </div>
+ </el-card>
</div>
</template>
<script>
import TaskMileageDetail from '@/components/TaskMileageDetail'
import { getTask } from '@/api/task'
+import { getPaymentInfo } from '@/api/task'
export default {
name: 'TaskDetail',
@@ -46,6 +96,7 @@
taskId: null,
taskCode: '',
taskStatus: '',
+ taskType: '',
vehicleNo: '',
assigneeName: '',
plannedStartTime: '',
@@ -54,13 +105,16 @@
actualEndTime: '',
departureAddress: '',
destinationAddress: ''
- }
+ },
+ paymentInfo: null,
+ showPaymentInfo: false
}
},
created() {
const taskId = this.$route.params && this.$route.params.taskId
if (taskId) {
this.loadTaskInfo(taskId)
+ this.loadPaymentInfo(taskId)
}
},
methods: {
@@ -68,6 +122,19 @@
loadTaskInfo(taskId) {
getTask(taskId).then(response => {
this.taskInfo = response.data
+ // 鍙湁杞繍浠诲姟鎵嶆樉绀烘敮浠樹俊鎭�
+ this.showPaymentInfo = this.taskInfo.taskType === 'EMERGENCY_TRANSFER'
+ })
+ },
+
+ /** 鍔犺浇鏀粯淇℃伅 */
+ loadPaymentInfo(taskId) {
+ getPaymentInfo(taskId).then(response => {
+ if (response.code === 200) {
+ this.paymentInfo = response.data
+ }
+ }).catch(error => {
+ console.error('鍔犺浇鏀粯淇℃伅澶辫触:', error)
})
},
@@ -100,6 +167,41 @@
'CANCELLED': '宸插彇娑�'
}
return statusMap[status] || status
+ },
+
+ /** 鑾峰彇鏀粯鐘舵�佺被鍨� */
+ getPaymentStatusType(status) {
+ const typeMap = {
+ 'UNPAID': 'info',
+ 'PENDING': 'warning',
+ 'PAID': 'success',
+ 'FAILED': 'danger',
+ 'REFUNDED': 'info'
+ }
+ return typeMap[status] || 'info'
+ },
+
+ /** 鑾峰彇鏀粯鐘舵�佹枃鏈� */
+ getPaymentStatusText(status) {
+ const textMap = {
+ 'UNPAID': '鏈敮浠�',
+ 'PENDING': '鏀粯涓�',
+ 'PAID': '宸叉敮浠�',
+ 'FAILED': '鏀粯澶辫触',
+ 'REFUNDED': '宸查��娆�'
+ }
+ return textMap[status] || status
+ },
+
+ /** 鑾峰彇鏀粯鏂瑰紡鏂囨湰 */
+ getPaymentMethodText(method) {
+ const methodMap = {
+ 'CASH': '鐜伴噾鏀粯',
+ 'ON_ACCOUNT': '鎸傝处鏀粯',
+ 'WECHAT': '寰俊鏀粯',
+ 'ALIPAY': '鏀粯瀹濇敮浠�'
+ }
+ return methodMap[method] || method
}
}
}
diff --git a/sql/PaidMoney.sql b/sql/PaidMoney.sql
new file mode 100644
index 0000000..d42bb8c
--- /dev/null
+++ b/sql/PaidMoney.sql
@@ -0,0 +1,16 @@
+create table PaidMoney(
+id int no 4 10 0 no (n/a) (n/a) NULL
+PaidMoneyClass comment "榛樿FI",
+ServiceOrdIDDt bigint comment "ServiceOrderID",
+DispatchOrdIDDt bigint comment "DispatchOrderID",
+PaidMoney money comment "閲戦",
+PaidMoneyType int comment "鏀粯绫诲瀷",
+PaidMoneyMono nvarchar comment "鐢ㄦ潵瀛樻斁鏀粯鍗曞彿,鏍煎紡锛氫氦鏄撴祦姘碵鏀粯涓撶敤]",
+PaidMoneyTime datetime comment "鏀粯鏃堕棿",
+PaidMoneyOaID int comment "鏀粯浜篛AID",
+PaidMoneyUnitID int comment "榛樿涓�0"
+PaidMoney_AP_ID int comment "纭ID",
+PaidMoney_AP_Time datetime comment "纭鏃堕棿"
+PaidMoney_AP_Check int comment "纭鐘舵�� 1琛ㄧず宸茬‘璁わ紝0琛ㄧず鏈‘璁�",
+PaidMoneyTimestamp nvarchar comment "纭鏃堕棿鎴�",
+)
\ No newline at end of file
diff --git a/sql/PaidMoney_Add.sql b/sql/PaidMoney_Add.sql
new file mode 100644
index 0000000..529e77c
--- /dev/null
+++ b/sql/PaidMoney_Add.sql
@@ -0,0 +1,12 @@
+create table PaidMoney_Add
+(
+id int no 4 10 0 no (n/a) (n/a) NULL
+ToServiceOrdID bigint comment '鏈嶅姟鍗旾D',
+ToDispatchOrdID bigint comment '璋冨害鍗旾D',
+AddMoneyType int comment '闄勫姞璐圭敤绫诲瀷',
+AddMoney money comment '闄勫姞璐圭敤',
+AddMoneyExplain nvarchar comment '闄勫姞璐圭敤璇存槑',
+AddMoneyTime datetime comment '闄勫姞璐圭敤鏃堕棿',
+AddMoneyOAID int comment '娣诲姞鐢ㄦ埛鐨凮AID',
+
+)
\ No newline at end of file
diff --git a/sql/add_emergency_resync_flag.sql b/sql/add_emergency_resync_flag.sql
new file mode 100644
index 0000000..cab9f3f
--- /dev/null
+++ b/sql/add_emergency_resync_flag.sql
@@ -0,0 +1,9 @@
+-- 涓簊ys_task_emergency琛ㄦ坊鍔爊eed_resync瀛楁
+-- 鐢ㄤ簬鏍囪杞﹁締鎴栦汉鍛樹慨鏀瑰悗闇�瑕侀噸鏂板悓姝ュ埌鏃х郴缁�
+
+ALTER TABLE sys_task_emergency
+ADD COLUMN need_resync TINYINT(1) DEFAULT 0 COMMENT '鏄惁闇�瑕侀噸鏂板悓姝ワ細0-涓嶉渶瑕侊紝1-闇�瑕侀噸鏂板悓姝ワ紙杞﹁締鎴栦汉鍛樺彉鏇达級';
+
+-- 娣诲姞绱㈠紩浠ヤ紭鍖栨煡璇㈤渶瑕侀噸鏂板悓姝ョ殑浠诲姟
+ALTER TABLE sys_task_emergency
+ADD INDEX idx_need_resync (need_resync, dispatch_sync_status);
diff --git a/sql/additional_fee_sync_update.sql b/sql/additional_fee_sync_update.sql
new file mode 100644
index 0000000..300c18c
--- /dev/null
+++ b/sql/additional_fee_sync_update.sql
@@ -0,0 +1,34 @@
+-- 闄勫姞璐圭敤鍚屾鍔熻兘 - 鏁版嵁琛ㄥ彉鏇�
+-- 浣滆��: ruoyi
+-- 鏃ユ湡: 2025-01-15
+
+-- =============================================
+-- 1. 淇敼鏂扮郴缁� sys_task_additional_fee 琛紝娣诲姞鍚屾鐩稿叧瀛楁
+-- =============================================
+ALTER TABLE sys_task_additional_fee ADD COLUMN pid BIGINT COMMENT '鏃х郴缁熼檮鍔犺垂鐢ㄨ褰旾D(PaidMoney_Add.id)' AFTER created_time;
+ALTER TABLE sys_task_additional_fee ADD COLUMN sync_status INT DEFAULT 0 COMMENT '鍚屾鐘舵�侊細0鏈悓姝ワ紝1鍚屾涓紝2鍚屾鎴愬姛锛�3鍚屾澶辫触' AFTER pid;
+ALTER TABLE sys_task_additional_fee ADD COLUMN sync_time DATETIME COMMENT '鍚屾鏃堕棿' AFTER sync_status;
+
+-- 涓簆id瀛楁娣诲姞绱㈠紩锛屾彁楂樻煡璇㈡晥鐜�
+CREATE INDEX idx_pid ON sys_task_additional_fee(pid);
+
+-- 涓簊ync_status瀛楁娣诲姞绱㈠紩锛岀敤浜庢壒閲忓悓姝ユ煡璇�
+CREATE INDEX idx_sync_status ON sys_task_additional_fee(sync_status);
+
+-- =============================================
+-- 璇存槑锛�
+-- =============================================
+-- 1. pid: 瀛樺偍鏃х郴缁烶aidMoney_Add琛ㄧ殑涓婚敭ID锛岀敤浜庡弻鍚戝叧鑱�
+-- 2. sync_status: 鍚屾鐘舵�佹爣璇�
+-- - 0: 鏈悓姝� - 鏂板垱寤虹殑闄勫姞璐圭敤璁板綍锛屽皻鏈悓姝ュ埌鏃х郴缁�
+-- - 1: 鍚屾涓� - 姝e湪鎵ц鍚屾鎿嶄綔
+-- - 2: 鍚屾鎴愬姛 - 宸叉垚鍔熷悓姝ュ埌鏃х郴缁�
+-- - 3: 鍚屾澶辫触 - 鍚屾杩囩▼涓彂鐢熼敊璇�
+-- 3. sync_time: 璁板綍鏈�鍚庝竴娆″悓姝ユ搷浣滅殑鏃堕棿
+--
+-- 鍚屾閫昏緫锛�
+-- - 鏂扮郴缁� -> 鏃х郴缁燂細鏂扮郴缁熸坊鍔犻檮鍔犺垂鐢ㄥ悗锛岃嚜鍔ㄥ悓姝ュ埌鏃х郴缁烶aidMoney_Add琛�
+-- - 鏃х郴缁� -> 鏂扮郴缁燂細瀹氭椂浠诲姟浠庢棫绯荤粺PaidMoney_Add琛ㄥ悓姝ユ渶鏂伴檮鍔犺垂鐢ㄨ褰曞埌鏂扮郴缁�
+-- - 閫氳繃ToServiceOrdID鍜孴oDispatchOrdID瀛楁鍏宠仈浠诲姟
+-- - 璐圭敤绫诲瀷鏄犲皠锛�
+-- 鏂扮郴缁熷瓧鍏稿��(1-绛夊緟璐�, 2-鎷呮灦, 3-灞呭ICU, 4-鍖荤枟璁惧) <-> 鏃х郴缁烝ddMoneyType(1,2,3,4)
diff --git a/sql/dryad_payment_tables.sql b/sql/dryad_payment_tables.sql
new file mode 100644
index 0000000..a879875
--- /dev/null
+++ b/sql/dryad_payment_tables.sql
@@ -0,0 +1,126 @@
+-- dryad-payment 鏀粯妯″潡鏁版嵁琛�
+-- 鍦ㄤ富鏁版嵁搴� 966120 涓垱寤烘敮浠樻ā鍧楁墍闇�鐨勮〃
+
+USE `966120`;
+
+-- 鏀粯璁㈠崟琛�
+CREATE TABLE IF NOT EXISTS `pay_order` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '璁㈠崟ID',
+ `biz_order_id` VARCHAR(64) NOT NULL COMMENT '涓氬姟璁㈠崟鍙�',
+ `amount` INT NOT NULL COMMENT '閲戦锛堝垎锛�',
+ `currency` VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '甯佺',
+ `channel` VARCHAR(16) NOT NULL COMMENT '鏀粯娓犻亾',
+ `status` VARCHAR(16) NOT NULL COMMENT '璁㈠崟鐘舵��',
+ `subject` VARCHAR(128) NOT NULL COMMENT '璁㈠崟鏍囬',
+ `description` VARCHAR(512) COMMENT '璁㈠崟鎻忚堪',
+ `callback_url` VARCHAR(512) NOT NULL COMMENT '涓氬姟鍥炶皟鍦板潃',
+ `expire_at` DATETIME NOT NULL COMMENT '杩囨湡鏃堕棿',
+ `latest_transaction_id` BIGINT COMMENT '鏈�鏂颁氦鏄揑D',
+ `channel_trade_no` VARCHAR(64) COMMENT '娓犻亾浜ゆ槗鍙�',
+ `paid_at` DATETIME COMMENT '鏀粯鎴愬姛鏃堕棿',
+ `version` INT NOT NULL DEFAULT 0 COMMENT '涔愯閿佺増鏈彿',
+ `created_at` DATETIME NOT NULL COMMENT '鍒涘缓鏃堕棿',
+ `updated_at` DATETIME NOT NULL COMMENT '鏇存柊鏃堕棿',
+ INDEX `idx_biz_order_id` (`biz_order_id`),
+ INDEX `idx_channel_trade_no` (`channel_trade_no`),
+ INDEX `idx_status` (`status`),
+ INDEX `idx_expire_at` (`expire_at`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鏀粯璁㈠崟琛�';
+
+-- 鏀粯浜ゆ槗娴佹按琛�
+CREATE TABLE IF NOT EXISTS `pay_transaction` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '浜ゆ槗ID',
+ `order_id` BIGINT NOT NULL COMMENT '璁㈠崟ID',
+ `channel` VARCHAR(16) NOT NULL COMMENT '鏀粯娓犻亾',
+ `client_type` VARCHAR(32) NOT NULL COMMENT '瀹㈡埛绔被鍨�',
+ `status` VARCHAR(16) NOT NULL COMMENT '浜ゆ槗鐘舵��',
+ `code_or_qr` VARCHAR(512) COMMENT '浜岀淮鐮佸唴瀹�',
+ `qr_base64` TEXT COMMENT 'Base64浜岀淮鐮佸浘鐗�',
+ `request_params` TEXT COMMENT '璇锋眰鍙傛暟蹇収',
+ `response_snapshot` TEXT COMMENT '鍝嶅簲蹇収',
+ `channel_trade_no` VARCHAR(64) COMMENT '娓犻亾浜ゆ槗鍙�',
+ `created_at` DATETIME NOT NULL COMMENT '鍒涘缓鏃堕棿',
+ `paid_at` DATETIME COMMENT '鏀粯瀹屾垚鏃堕棿',
+ INDEX `idx_order_id` (`order_id`),
+ INDEX `idx_channel_trade_no` (`channel_trade_no`),
+ INDEX `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鏀粯浜ゆ槗娴佹按琛�';
+
+-- 娓犻亾鍥炶皟鏃ュ織琛�
+CREATE TABLE IF NOT EXISTS `pay_notify_log` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '鏃ュ織ID',
+ `channel` VARCHAR(16) NOT NULL COMMENT '鏀粯娓犻亾',
+ `notify_id_or_serial` VARCHAR(64) NOT NULL COMMENT '娓犻亾閫氱煡鍞竴鏍囪瘑',
+ `order_id` BIGINT COMMENT '璁㈠崟ID',
+ `transaction_id` BIGINT COMMENT '浜ゆ槗ID',
+ `payload` TEXT NOT NULL COMMENT '鍥炶皟鍘熷鎶ユ枃',
+ `verified` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '绛惧悕楠岃瘉鏄惁閫氳繃',
+ `processed` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '鏄惁宸插鐞�',
+ `result` VARCHAR(128) COMMENT '澶勭悊缁撴灉',
+ `created_at` DATETIME NOT NULL COMMENT '鎺ユ敹鏃堕棿',
+ UNIQUE KEY `uk_channel_notify` (`channel`, `notify_id_or_serial`),
+ INDEX `idx_order_id` (`order_id`),
+ INDEX `idx_transaction_id` (`transaction_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='娓犻亾鍥炶皟鏃ュ織琛�';
+
+-- 涓氬姟鍥炶皟鏃ュ織琛�
+CREATE TABLE IF NOT EXISTS `pay_biz_callback_log` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '鏃ュ織ID',
+ `order_id` BIGINT NOT NULL COMMENT '璁㈠崟ID',
+ `transaction_id` BIGINT NOT NULL COMMENT '浜ゆ槗ID',
+ `callback_url` VARCHAR(512) NOT NULL COMMENT '鍥炶皟鍦板潃',
+ `payload` TEXT NOT NULL COMMENT '鍥炶皟璇锋眰浣�',
+ `http_status` INT COMMENT 'HTTP鐘舵�佺爜',
+ `response` TEXT COMMENT '鍝嶅簲鍐呭',
+ `success` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '鏄惁鎴愬姛',
+ `retry_count` INT NOT NULL DEFAULT 0 COMMENT '閲嶈瘯娆℃暟',
+ `last_retry_at` DATETIME COMMENT '鏈�鍚庨噸璇曟椂闂�',
+ `created_at` DATETIME NOT NULL COMMENT '鍒涘缓鏃堕棿',
+ INDEX `idx_order_id` (`order_id`),
+ INDEX `idx_success` (`success`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='涓氬姟鍥炶皟鏃ュ織琛�';
+
+-- 鎿嶄綔瀹¤琛�
+CREATE TABLE IF NOT EXISTS `pay_operation_audit` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '瀹¤ID',
+ `operator` VARCHAR(64) NOT NULL COMMENT '鎿嶄綔浜�',
+ `operation_type` VARCHAR(32) NOT NULL COMMENT '鎿嶄綔绫诲瀷',
+ `order_id` BIGINT COMMENT '璁㈠崟ID',
+ `transaction_id` BIGINT COMMENT '浜ゆ槗ID',
+ `params` TEXT COMMENT '鎿嶄綔鍙傛暟',
+ `approved` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '鏄惁閫氳繃',
+ `created_at` DATETIME NOT NULL COMMENT '鎿嶄綔鏃堕棿',
+ INDEX `idx_operator` (`operator`),
+ INDEX `idx_operation_type` (`operation_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鎿嶄綔瀹¤琛�';
+
+-- 瀵硅处浠诲姟琛�
+CREATE TABLE IF NOT EXISTS `pay_reconciliation_task` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '浠诲姟ID',
+ `task_date` DATE NOT NULL COMMENT '瀵硅处鏃ユ湡',
+ `status` VARCHAR(16) NOT NULL COMMENT '浠诲姟鐘舵��',
+ `total_count` INT NOT NULL DEFAULT 0 COMMENT '鎬昏鍗曟暟',
+ `success_count` INT NOT NULL DEFAULT 0 COMMENT '鎴愬姛鏁�',
+ `failed_count` INT NOT NULL DEFAULT 0 COMMENT '澶辫触鏁�',
+ `diff_count` INT NOT NULL DEFAULT 0 COMMENT '宸紓鏁�',
+ `fixed_count` INT NOT NULL DEFAULT 0 COMMENT '鑷姩淇鏁�',
+ `created_at` DATETIME NOT NULL COMMENT '鍒涘缓鏃堕棿',
+ `finished_at` DATETIME COMMENT '瀹屾垚鏃堕棿',
+ UNIQUE KEY `uk_task_date` (`task_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='瀵硅处浠诲姟琛�';
+
+-- 瀵硅处宸紓鏄庣粏琛�
+CREATE TABLE IF NOT EXISTS `pay_reconciliation_result` (
+ `id` BIGINT NOT NULL PRIMARY KEY COMMENT '鏄庣粏ID',
+ `task_id` BIGINT NOT NULL COMMENT '浠诲姟ID',
+ `order_id` BIGINT NOT NULL COMMENT '璁㈠崟ID',
+ `transaction_id` BIGINT COMMENT '浜ゆ槗ID',
+ `local_status` VARCHAR(16) COMMENT '鏈湴鐘舵��',
+ `channel_status` VARCHAR(16) COMMENT '娓犻亾鐘舵��',
+ `diff_type` VARCHAR(32) NOT NULL COMMENT '宸紓绫诲瀷',
+ `fixed` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '鏄惁宸蹭慨澶�',
+ `note` VARCHAR(512) COMMENT '澶囨敞',
+ `created_at` DATETIME NOT NULL COMMENT '鍒涘缓鏃堕棿',
+ INDEX `idx_task_id` (`task_id`),
+ INDEX `idx_order_id` (`order_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='瀵硅处宸紓鏄庣粏琛�';
diff --git a/sql/payment_sync_job.sql b/sql/payment_sync_job.sql
new file mode 100644
index 0000000..cca2113
--- /dev/null
+++ b/sql/payment_sync_job.sql
@@ -0,0 +1,113 @@
+-- 鏀粯淇℃伅鍜岄檮鍔犺垂鐢ㄥ悓姝ュ畾鏃朵换鍔¢厤缃甋QL
+-- 鍦╯ys_job琛ㄤ腑娣诲姞瀹氭椂浠诲姟
+
+-- 1. 鏀粯淇℃伅鍚屾锛堟柊绯荤粺 -> 鏃х郴缁燂級
+INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark)
+VALUES
+('鏀粯淇℃伅鍚屾鍒版棫绯荤粺', 'DEFAULT', 'legacySystemSyncTask.syncPaymentToLegacy', '0 0/10 * * * ?', '3', '1', '0', 'admin', sysdate(),
+'姣�10鍒嗛挓鑷姩鍚屾鏂扮郴缁熸湭鍚屾鐨勬敮浠樻垚鍔熻褰曞埌鏃х郴缁烶aidMoney琛ㄣ�傚悓姝ユ潯浠讹細pay_status=PAID涓攕ync_status=0鎴�3銆�');
+
+-- 2. 鏀粯淇℃伅鍙嶅悜鍚屾锛堟棫绯荤粺 -> 鏂扮郴缁燂級
+INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark)
+VALUES
+('鏀粯淇℃伅浠庢棫绯荤粺鍚屾', 'DEFAULT', 'legacySystemSyncTask.syncPaymentFromLegacy', '0 0/15 * * * ?', '3', '1', '0', 'admin', sysdate(),
+'姣�15鍒嗛挓鑷姩鍚屾鏃х郴缁烶aidMoney琛ㄦ渶杩�7澶╃殑鏀粯璁板綍鍒版柊绯荤粺銆傚彧鍚屾鍦ㄦ柊绯荤粺涓瓨鍦ㄥ搴旇浆杩愪换鍔★紙ServiceOrdID鍜孌ispatchOrdID鍖归厤锛夌殑璁板綍銆�');
+
+-- 3. 闄勫姞璐圭敤鍚屾锛堟柊绯荤粺 -> 鏃х郴缁燂級
+INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark)
+VALUES
+('闄勫姞璐圭敤鍚屾鍒版棫绯荤粺', 'DEFAULT', 'legacySystemSyncTask.syncAdditionalFeeToLegacy', '0 0/10 * * * ?', '3', '1', '0', 'admin', sysdate(),
+'姣�10鍒嗛挓鑷姩鍚屾鏂扮郴缁熸湭鍚屾鐨勯檮鍔犺垂鐢ㄨ褰曞埌鏃х郴缁烶aidMoney_Add琛ㄣ�傚悓姝ユ潯浠讹細sync_status=0鎴�3銆�');
+
+-- 4. 闄勫姞璐圭敤鍙嶅悜鍚屾锛堟棫绯荤粺 -> 鏂扮郴缁燂級
+INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark)
+VALUES
+('闄勫姞璐圭敤浠庢棫绯荤粺鍚屾', 'DEFAULT', 'legacySystemSyncTask.syncAdditionalFeeFromLegacy', '0 0/15 * * * ?', '3', '1', '0', 'admin', sysdate(),
+'姣�15鍒嗛挓鑷姩鍚屾鏃х郴缁烶aidMoney_Add琛ㄦ渶杩�24灏忔椂鐨勯檮鍔犺垂鐢ㄨ褰曞埌鏂扮郴缁熴�傚彧鍚屾鍦ㄦ柊绯荤粺涓瓨鍦ㄥ搴旇浆杩愪换鍔★紙ServiceOrdID鍜孌ispatchOrdID鍖归厤锛夌殑璁板綍銆�');
+
+-- 璇存槑锛�
+-- job_name: 浠诲姟鍚嶇О
+-- job_group: 浠诲姟缁勫悕锛圖EFAULT涓洪粯璁ょ粍锛�
+-- invoke_target: 璋冪敤鐩爣瀛楃涓诧紙Bean鍚嶇О.鏂规硶鍚嶏級
+-- 鏀粯鍚屾鏂规硶锛�
+-- - legacySystemSyncTask.syncPaymentToLegacy() 鏂扮郴缁� -> 鏃х郴缁�
+-- - legacySystemSyncTask.syncPaymentFromLegacy() 鏃х郴缁� -> 鏂扮郴缁�
+-- 闄勫姞璐圭敤鍚屾鏂规硶锛�
+-- - legacySystemSyncTask.syncAdditionalFeeToLegacy() 鏂扮郴缁� -> 鏃х郴缁�
+-- - legacySystemSyncTask.syncAdditionalFeeFromLegacy() 鏃х郴缁� -> 鏂扮郴缁�
+--
+-- cron_expression: cron琛ㄨ揪寮�
+-- - '0 0/10 * * * ?' = 姣�10鍒嗛挓鎵ц涓�娆★紙鎺ㄨ崘鐢ㄤ簬鏂扮郴缁熷埌鏃х郴缁熺殑鍚屾锛�
+-- - '0 0/15 * * * ?' = 姣�15鍒嗛挓鎵ц涓�娆★紙鎺ㄨ崘鐢ㄤ簬鏃х郴缁熷埌鏂扮郴缁熺殑鍚屾锛�
+-- - '0 0/5 * * * ?' = 姣�5鍒嗛挓鎵ц涓�娆★紙浠呭湪鍚屾瑕佹眰楂樻椂浣跨敤锛�
+-- - '0 0/30 * * * ?' = 姣�30鍒嗛挓鎵ц涓�娆�
+--
+-- misfire_policy: 閿欒繃鎵ц绛栫暐
+-- - 1=绔嬪嵆鎵ц
+-- - 2=鎵ц涓�娆�
+-- - 3=鏀惧純鎵ц锛堟帹鑽愶級
+--
+-- concurrent: 鏄惁骞跺彂
+-- - 0=鍏佽骞跺彂
+-- - 1=绂佹骞跺彂锛堟帹鑽愶紝閬垮厤閲嶅鍚屾鍜屾暟鎹啿绐侊級
+--
+-- status: 鐘舵��
+-- - 0=姝e父锛堝惎鐢級
+-- - 1=鏆傚仠锛堝仠鐢級
+
+-- 浣跨敤寤鸿锛�
+-- 1. 鏂扮郴缁熷埌鏃х郴缁熷悓姝ワ紙10鍒嗛挓锛夛細
+-- - 鐢ㄦ埛鎿嶄綔鍚庨渶蹇�熷悓姝ュ埌鏃х郴缁熶緵鍏朵粬涓氬姟浣跨敤
+-- - 鏈悓姝ヨ褰曚細鑷姩閲嶈瘯
+--
+-- 2. 鏃х郴缁熷埌鏂扮郴缁熷悓姝ワ紙15鍒嗛挓锛夛細
+-- - 浠呭悓姝ュ瓨鍦ㄥ搴旇浆杩愪换鍔$殑璁板綍锛堥獙璇丼erviceOrdID鍜孌ispatchOrdID锛�
+-- - 鏀粯璁板綍鏌ヨ鏈�杩�7澶�
+-- - 闄勫姞璐圭敤鏌ヨ鏈�杩�24灏忔椂
+-- - 閬垮厤鍚屾鏃犳晥鐨勫绔嬫暟鎹�
+--
+-- 3. 鎬ц兘浼樺寲锛�
+-- - 姣忔壒娆¢檺鍒�100鏉¤褰�
+-- - 姣忔潯璁板綍闂撮殧1绉�
+-- - 绂佹骞跺彂鎵ц
+--
+-- 4. 鐩戞帶寤鸿锛�
+-- - 瀹氭湡妫�鏌ync_status=3鐨勫け璐ヨ褰�
+-- - 鏌ョ湅瀹氭椂浠诲姟鎵ц鏃ュ織
+-- - 鐩戞帶鍚屾鑰楁椂鍜屾垚鍔熺巼
+
+-- 鐩戞帶鏌ヨ绀轰緥锛�
+
+-- 鏌ョ湅鏀粯璁板綍鍚屾鐘舵�侊細
+-- SELECT
+-- sync_status,
+-- CASE sync_status
+-- WHEN 0 THEN '鏈悓姝�'
+-- WHEN 1 THEN '鍚屾涓�'
+-- WHEN 2 THEN '鍚屾鎴愬姛'
+-- WHEN 3 THEN '鍚屾澶辫触'
+-- END AS status_name,
+-- COUNT(*) AS count
+-- FROM sys_task_payment
+-- WHERE pay_status = 'PAID'
+-- GROUP BY sync_status;
+
+-- 鏌ョ湅闄勫姞璐圭敤鍚屾鐘舵�侊細
+-- SELECT
+-- sync_status,
+-- CASE sync_status
+-- WHEN 0 THEN '鏈悓姝�'
+-- WHEN 1 THEN '鍚屾涓�'
+-- WHEN 2 THEN '鍚屾鎴愬姛'
+-- WHEN 3 THEN '鍚屾澶辫触'
+-- END AS status_name,
+-- COUNT(*) AS count
+-- FROM sys_task_additional_fee
+-- GROUP BY sync_status;
+
+-- 鏌ョ湅浠婃棩鍚屾澶辫触鐨勮褰曪細
+-- SELECT * FROM sys_task_payment
+-- WHERE sync_status = 3 AND DATE(sync_time) = CURDATE();
+
+-- SELECT * FROM sys_task_additional_fee
+-- WHERE sync_status = 3 AND DATE(sync_time) = CURDATE();
diff --git a/sql/payment_sync_update.sql b/sql/payment_sync_update.sql
new file mode 100644
index 0000000..5b0a2cc
--- /dev/null
+++ b/sql/payment_sync_update.sql
@@ -0,0 +1,34 @@
+-- 鏀粯淇℃伅鍙屽悜鍚屾鍔熻兘 - 鏁版嵁琛ㄥ彉鏇�
+-- 浣滆��: ruoyi
+-- 鏃ユ湡: 2025-01-15
+
+-- =============================================
+-- 1. 淇敼鏂扮郴缁� sys_task_payment 琛紝娣诲姞鍚屾鐩稿叧瀛楁
+-- =============================================
+ALTER TABLE sys_task_payment ADD COLUMN pid BIGINT COMMENT '鏃х郴缁熸敮浠樿褰旾D(PaidMoney.id)' AFTER update_time;
+ALTER TABLE sys_task_payment ADD COLUMN sync_status INT DEFAULT 0 COMMENT '鍚屾鐘舵�侊細0鏈悓姝ワ紝1鍚屾涓紝2鍚屾鎴愬姛锛�3鍚屾澶辫触' AFTER pid;
+ALTER TABLE sys_task_payment ADD COLUMN sync_time DATETIME COMMENT '鍚屾鏃堕棿' AFTER sync_status;
+
+-- 涓簆id瀛楁娣诲姞绱㈠紩锛屾彁楂樻煡璇㈡晥鐜�
+CREATE INDEX idx_pid ON sys_task_payment(pid);
+
+-- 涓簊ync_status瀛楁娣诲姞绱㈠紩锛岀敤浜庢壒閲忓悓姝ユ煡璇�
+CREATE INDEX idx_sync_status ON sys_task_payment(sync_status);
+
+-- =============================================
+-- 璇存槑锛�
+-- =============================================
+-- 1. pid: 瀛樺偍鏃х郴缁烶aidMoney琛ㄧ殑涓婚敭ID锛岀敤浜庡弻鍚戝叧鑱�
+-- 2. sync_status: 鍚屾鐘舵�佹爣璇�
+-- - 0: 鏈悓姝� - 鏂板垱寤虹殑鏀粯璁板綍锛屽皻鏈悓姝ュ埌鏃х郴缁�
+-- - 1: 鍚屾涓� - 姝e湪鎵ц鍚屾鎿嶄綔
+-- - 2: 鍚屾鎴愬姛 - 宸叉垚鍔熷悓姝ュ埌鏃х郴缁�
+-- - 3: 鍚屾澶辫触 - 鍚屾杩囩▼涓彂鐢熼敊璇�
+-- 3. sync_time: 璁板綍鏈�鍚庝竴娆″悓姝ユ搷浣滅殑鏃堕棿
+--
+-- 鍚屾閫昏緫锛�
+-- - 鏂扮郴缁� -> 鏃х郴缁燂細鏂扮郴缁熸敮浠樻垚鍔熷悗锛岃嚜鍔ㄥ悓姝ュ埌鏃х郴缁烶aidMoney琛�
+-- - 鏃х郴缁� -> 鏂扮郴缁燂細瀹氭椂浠诲姟浠庢棫绯荤粺PaidMoney琛ㄥ悓姝ユ渶鏂版敮浠樿褰曞埌鏂扮郴缁�
+-- - 閫氳繃ServiceOrdIDDt鍜孌ispatchOrdIDDt瀛楁鍏宠仈浠诲姟
+-- - 鏀粯鏂瑰紡鏄犲皠锛�
+-- 鏂扮郴缁�(CASH/ON_ACCOUNT/WECHAT/ALIPAY) <-> 鏃х郴缁�(1鐜伴噾/6鎸傝处/3寰俊/4鏀粯瀹�)
diff --git a/sql/resync_vehicle_personnel_job.sql b/sql/resync_vehicle_personnel_job.sql
new file mode 100644
index 0000000..128b77b
--- /dev/null
+++ b/sql/resync_vehicle_personnel_job.sql
@@ -0,0 +1,96 @@
+-- 杞﹁締鍜屼汉鍛樺彉鏇撮噸鏂板悓姝ュ畾鏃朵换鍔¢厤缃甋QL
+-- 鍦╯ys_job琛ㄤ腑娣诲姞瀹氭椂浠诲姟
+-- 璋冪敤鏃х郴缁� admin_save_25.asp 鎺ュ彛鏇存柊璋冨害鍗�
+--
+-- 闇�瑕侀噸鏂板悓姝ョ殑鍙樻洿绫诲瀷锛�
+-- 1. 杞﹁締鍙樻洿锛氫慨鏀逛簡鍒嗛厤鐨勮溅杈�
+-- 2. 鎵ц浜哄憳鍙樻洿锛氫慨鏀逛簡鎵ц浜哄憳锛堝徃鏈恒�佸尰鐢熴�佹姢澹級
+-- 3. 鍦板潃鍙樻洿锛氫慨鏀逛簡杞嚭鍖婚櫌鍦板潃鎴栬浆鍏ュ尰闄㈠湴鍧�
+-- 4. 鎴愪氦浠峰彉鏇达細淇敼浜嗚浆杩愯垂鐢紙transfer_price锛�
+
+-- 閲嶆柊鍚屾杞﹁締鍜屼汉鍛樺彉鏇寸殑浠诲姟鍒版棫绯荤粺
+INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark)
+VALUES
+('閲嶆柊鍚屾杞﹁締浜哄憳鍙樻洿', 'DEFAULT', 'legacySystemSyncTask.resyncVehicleAndPersonnel', '0 0/5 * * * ?', '3', '1', '0', 'admin', sysdate(),
+'姣�5鍒嗛挓鑷姩閲嶆柊鍚屾杞﹁締銆佷汉鍛樸�佸湴鍧�銆佹垚浜や环鍙戠敓鍙樻洿鐨勪换鍔″埌鏃х郴缁熴�傚綋浠诲姟鐨勮溅杈嗐�佷汉鍛樸�佸湴鍧�鎴栬垂鐢ㄨ淇敼鍚庯紝浼氭爣璁皀eed_resync=1锛屽畾鏃朵换鍔′細灏嗚繖浜涘彉鏇村悓姝ュ埌鏃х郴缁熻皟搴﹀崟琛ㄣ��');
+
+-- 璇存槑锛�
+-- job_name: 浠诲姟鍚嶇О
+-- job_group: 浠诲姟缁勫悕锛圖EFAULT涓洪粯璁ょ粍锛�
+-- invoke_target: 璋冪敤鐩爣瀛楃涓诧紙Bean鍚嶇О.鏂规硶鍚嶏級
+-- - legacySystemSyncTask.resyncVehicleAndPersonnel() 閲嶆柊鍚屾杞﹁締鍜屼汉鍛樺彉鏇�
+--
+-- cron_expression: cron琛ㄨ揪寮�
+-- - '0 0/5 * * * ?' = 姣�5鍒嗛挓鎵ц涓�娆★紙鎺ㄨ崘锛�
+-- - '0 0/10 * * * ?' = 姣�10鍒嗛挓鎵ц涓�娆�
+-- - '0 0/3 * * * ?' = 姣�3鍒嗛挓鎵ц涓�娆★紙浠呭湪鍙樻洿棰戠箒鏃朵娇鐢級
+--
+-- misfire_policy: 閿欒繃鎵ц绛栫暐
+-- - 1=绔嬪嵆鎵ц
+-- - 2=鎵ц涓�娆�
+-- - 3=鏀惧純鎵ц锛堟帹鑽愶級
+--
+-- concurrent: 鏄惁骞跺彂
+-- - 0=鍏佽骞跺彂
+-- - 1=绂佹骞跺彂锛堟帹鑽愶紝閬垮厤閲嶅鍚屾锛�
+--
+-- status: 鐘舵��
+-- - 0=姝e父锛堝惎鐢級
+-- - 1=鏆傚仠锛堝仠鐢級
+
+-- 浣跨敤鍦烘櫙锛�
+-- 1. 杞﹁締淇℃伅鍙樻洿锛�
+-- - 浠诲姟鍒嗛厤鐨勮溅杈嗚鏇存崲
+-- - 杞﹁締淇℃伅锛堣溅鐗屽彿绛夛級琚慨鏀�
+--
+-- 2. 浜哄憳淇℃伅鍙樻洿锛�
+-- - 鎵ц浜哄憳锛堝徃鏈恒�佸尰鐢熴�佹姢澹瓑锛夎閲嶆柊鍒嗛厤
+-- - 浜哄憳淇℃伅琚慨鏀�
+--
+-- 3. 鍦板潃淇℃伅鍙樻洿锛�
+-- - 杞嚭鍖婚櫌鍦板潃琚慨鏀�
+-- - 杞叆鍖婚櫌鍦板潃琚慨鏀�
+--
+-- 4. 鎴愪氦浠峰彉鏇达細
+-- - 杞繍璐圭敤锛坱ransfer_price锛夎淇敼
+--
+-- 3. 鍚屾娴佺▼锛�
+-- - 鍦ㄤ慨鏀硅溅杈嗐�佷汉鍛樸�佸湴鍧�鎴栨垚浜や环鏃讹紝璁剧疆 need_resync = 1
+-- - 瀹氭椂浠诲姟鏌ヨ need_resync = 1 涓� dispatch_sync_status = 2 鐨勪换鍔�
+-- - 璋冪敤鏃х郴缁� admin_save_25.asp 鎺ュ彛閲嶆柊鍚屾璋冨害鍗曚俊鎭紙鑰岄潪 admin_save_24.gds锛�
+-- - 鍚屾鎴愬姛鍚庤缃� need_resync = 0
+--
+-- 4. 鎬ц兘浼樺寲锛�
+-- - 姣忔壒娆¢檺鍒�100鏉¤褰�
+-- - 姣忔潯璁板綍闂撮殧1绉�
+-- - 绂佹骞跺彂鎵ц
+-- - 鍙悓姝ユ湭瀹屾垚鍜屾湭鍙栨秷鐨勪换鍔�
+
+-- 鐩戞帶鏌ヨ绀轰緥锛�
+
+-- 鏌ョ湅闇�瑕侀噸鏂板悓姝ョ殑浠诲姟鏁伴噺锛�
+-- SELECT COUNT(*) FROM sys_task_emergency
+-- WHERE need_resync = 1
+-- AND dispatch_sync_status = 2
+-- AND legacy_dispatch_ord_id IS NOT NULL;
+
+-- 鏌ョ湅閲嶆柊鍚屾鐘舵�佺粺璁★細
+-- SELECT
+-- need_resync,
+-- CASE need_resync
+-- WHEN 0 THEN '鏃犻渶閲嶆柊鍚屾'
+-- WHEN 1 THEN '闇�瑕侀噸鏂板悓姝�'
+-- END AS resync_status,
+-- COUNT(*) AS count
+-- FROM sys_task_emergency
+-- WHERE dispatch_sync_status = 2
+-- GROUP BY need_resync;
+
+-- 鏌ョ湅浠婃棩閲嶆柊鍚屾澶辫触鐨勮褰曪細
+-- SELECT te.*, t.task_code, t.task_status
+-- FROM sys_task_emergency te
+-- JOIN sys_task t ON te.task_id = t.task_id
+-- WHERE te.need_resync = 1
+-- AND te.dispatch_sync_status = 2
+-- AND DATE(te.dispatch_sync_time) = CURDATE()
+-- AND te.dispatch_sync_error_msg IS NOT NULL;
diff --git a/sql/sys_dept_add_departure_fields.sql b/sql/sys_dept_add_departure_fields.sql
new file mode 100644
index 0000000..c1bbf33
--- /dev/null
+++ b/sql/sys_dept_add_departure_fields.sql
@@ -0,0 +1,14 @@
+-- 涓� sys_dept 琛ㄥ鍔犲嚭鍙戝湴鐩稿叧瀛楁
+-- 鐢ㄤ簬閰嶇疆鍚勫垎鍏徃鐨勯粯璁ゅ嚭杞﹀湴鍧�
+
+-- 澧炲姞鍑鸿溅鍦板潃瀛楁
+ALTER TABLE sys_dept ADD COLUMN departure_address VARCHAR(500) COMMENT '榛樿鍑鸿溅鍦板潃';
+
+-- 澧炲姞鍑鸿溅鍦板潃缁忓害
+ALTER TABLE sys_dept ADD COLUMN departure_longitude DECIMAL(10, 6) COMMENT '鍑鸿溅鍦板潃缁忓害';
+
+-- 澧炲姞鍑鸿溅鍦板潃绾害
+ALTER TABLE sys_dept ADD COLUMN departure_latitude DECIMAL(10, 6) COMMENT '鍑鸿溅鍦板潃绾害';
+
+-- 鏇存柊澶囨敞
+ALTER TABLE sys_dept MODIFY COLUMN remark VARCHAR(500) COMMENT '澶囨敞(鍙厤缃粯璁ゅ嚭杞﹀湴鍧�绛変俊鎭�)';
diff --git a/sql/task_payment_method_dict.sql b/sql/task_payment_method_dict.sql
new file mode 100644
index 0000000..15680d9
--- /dev/null
+++ b/sql/task_payment_method_dict.sql
@@ -0,0 +1,15 @@
+-- 浠诲姟鏀粯鏂瑰紡瀛楀吀绫诲瀷
+INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
+VALUES ('浠诲姟鏀粯鏂瑰紡', 'task_payment_method', '0', 'admin', NOW(), '浠诲姟缁撶畻鏀粯鏂瑰紡瀛楀吀');
+
+-- 浠诲姟鏀粯鏂瑰紡瀛楀吀鏁版嵁
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) VALUES
+(1, '鐜伴噾', '1', 'task_payment_method', '', 'primary', 'N', '0', 'admin', NOW(), '鐜伴噾鏀粯'),
+(2, '閾惰杞处', '2', 'task_payment_method', '', 'info', 'N', '0', 'admin', NOW(), '閾惰杞处鏀粯'),
+(3, '寰俊鏀粯', '3', 'task_payment_method', '', 'success', 'N', '0', 'admin', NOW(), '寰俊鎵爜鏀粯'),
+(4, '鏀粯瀹�', '4', 'task_payment_method', '', 'primary', 'N', '0', 'admin', NOW(), '鏀粯瀹濇壂鐮佹敮浠�'),
+(5, 'POS鏀舵', '5', 'task_payment_method', '', 'warning', 'N', '0', 'admin', NOW(), 'POS鏈哄埛鍗℃敹娆�'),
+(6, '鎸傝处', '6', 'task_payment_method', '', 'danger', 'N', '0', 'admin', NOW(), '鎸傝处鏀粯'),
+(7, '鏄撳尰閫氭寕璐�', '7', 'task_payment_method', '', 'danger', 'N', '0', 'admin', NOW(), '鏄撳尰閫氭寕璐︽敮浠�'),
+(8, '閫�娆�', '8', 'task_payment_method', '', 'default', 'N', '0', 'admin', NOW(), '閫�娆炬搷浣�(鍓嶇涓嶆樉绀�)'),
+(9, '绉垎', '9', 'task_payment_method', '', 'default', 'N', '0', 'admin', NOW(), '绉垎鏀粯(鍓嶇涓嶆樉绀�)');
diff --git a/sql/task_payment_tables.sql b/sql/task_payment_tables.sql
new file mode 100644
index 0000000..d051761
--- /dev/null
+++ b/sql/task_payment_tables.sql
@@ -0,0 +1,53 @@
+-- 闄勫姞璐圭敤鏄庣粏琛�
+CREATE TABLE sys_task_additional_fee (
+ id BIGINT NOT NULL AUTO_INCREMENT COMMENT '涓婚敭ID',
+ task_id BIGINT NOT NULL COMMENT '浠诲姟ID',
+ fee_type VARCHAR(50) NOT NULL COMMENT '璐圭敤绫诲瀷(瀛楀吀task_additional_fee_type)',
+ fee_name VARCHAR(100) NOT NULL COMMENT '璐圭敤鍚嶇О',
+ unit_amount DECIMAL(10,2) NOT NULL COMMENT '鍗曚环',
+ quantity INT NOT NULL DEFAULT 1 COMMENT '鏁伴噺',
+ total_amount DECIMAL(10,2) NOT NULL COMMENT '鎬婚噾棰�',
+ remark VARCHAR(500) COMMENT '澶囨敞',
+ created_by VARCHAR(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ created_time DATETIME DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+ PRIMARY KEY (id),
+ KEY idx_task_id (task_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='浠诲姟闄勫姞璐圭敤鏄庣粏琛�';
+
+-- 浠诲姟鏀粯璁板綍琛�
+CREATE TABLE sys_task_payment (
+ id BIGINT NOT NULL AUTO_INCREMENT COMMENT '涓婚敭ID',
+ task_id BIGINT NOT NULL COMMENT '浠诲姟ID',
+ total_amount DECIMAL(10,2) NOT NULL COMMENT '鎬婚噾棰�(鎴愪氦浠�+闄勫姞璐�)',
+ settlement_amount DECIMAL(10,2) NOT NULL COMMENT '缁撶畻閲戦',
+ payment_method VARCHAR(20) NOT NULL COMMENT '鏀粯鏂瑰紡:CASH鐜伴噾,ON_ACCOUNT鎸傚笎,WECHAT寰俊,ALIPAY鏀粯瀹�',
+ pay_status VARCHAR(20) NOT NULL DEFAULT 'UNPAID' COMMENT '鏀粯鐘舵��:UNPAID鏈敮浠�,PENDING寰呮敮浠�,PAID宸叉敮浠�,FAILED澶辫触,REFUNDED宸查��娆�',
+ pay_time DATETIME COMMENT '鏀粯鏃堕棿',
+ out_trade_no VARCHAR(64) COMMENT '鍟嗘埛璁㈠崟鍙�',
+ trade_no VARCHAR(64) COMMENT '涓夋柟浜ゆ槗鍙�',
+ code_url TEXT COMMENT '浜岀淮鐮侀摼鎺�',
+ qr_expire_time DATETIME COMMENT '浜岀淮鐮佽繃鏈熸椂闂�',
+ provider VARCHAR(20) COMMENT '鏀粯鎻愪緵鍟�:WECHAT,ALIPAY',
+ payment_ref_id VARCHAR(100) COMMENT '鏀粯妯″潡杩斿洖鐨勫敮涓�鏍囪瘑',
+ callback_url VARCHAR(500) COMMENT '鍥炶皟鍦板潃',
+ remark VARCHAR(500) COMMENT '澶囨敞',
+ created_by VARCHAR(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+ created_time DATETIME DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+ update_time DATETIME DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+ PRIMARY KEY (id),
+ UNIQUE KEY uk_out_trade_no (out_trade_no),
+ KEY idx_task_id (task_id),
+ KEY idx_pay_status (pay_status),
+ KEY idx_payment_ref_id (payment_ref_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='浠诲姟鏀粯璁板綍琛�';
+
+-- 闄勫姞璐圭敤绫诲瀷瀛楀吀
+INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
+VALUES ('浠诲姟闄勫姞璐圭敤绫诲瀷', 'task_additional_fee_type', '0', 'admin', NOW(), '杞繍浠诲姟闄勫姞璐圭敤绫诲瀷瀛楀吀');
+
+-- 闄勫姞璐圭敤绫诲瀷瀛楀吀鏁版嵁
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) VALUES
+(1, '绛夊緟璐�', '1', 'task_additional_fee_type', '', 'default', 'N', '0', 'admin', NOW(), '绛夊緟璐�'),
+(2, '鎷呮灦', '2', 'task_additional_fee_type', '', 'default', 'N', '0', 'admin', NOW(), '鎷呮灦'),
+(3, '灞呭ICU', '3', 'task_additional_fee_type', '', 'default', 'N', '0', 'admin', NOW(), '灞呭ICU'),
+(4, '鍖荤枟璁惧', '4', 'task_additional_fee_type', '', 'default', 'N', '0', 'admin', NOW(), '鍖荤枟璁惧');
diff --git a/sql/update_additional_fee_type_dict.sql b/sql/update_additional_fee_type_dict.sql
new file mode 100644
index 0000000..1785df9
--- /dev/null
+++ b/sql/update_additional_fee_type_dict.sql
@@ -0,0 +1,10 @@
+-- 鏇存柊闄勫姞璐圭敤绫诲瀷瀛楀吀鏁版嵁
+-- 鍒犻櫎鏃х殑瀛楀吀鏁版嵁
+DELETE FROM sys_dict_data WHERE dict_type = 'task_additional_fee_type';
+
+-- 鎻掑叆鏂扮殑瀛楀吀鏁版嵁
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) VALUES
+(1, '绛夊緟璐�', '1', 'task_additional_fee_type', '', 'default', 'N', '0', 'admin', NOW(), '绛夊緟璐�'),
+(2, '鎷呮灦', '2', 'task_additional_fee_type', '', 'default', 'N', '0', 'admin', NOW(), '鎷呮灦'),
+(3, '灞呭ICU', '3', 'task_additional_fee_type', '', 'default', 'N', '0', 'admin', NOW(), '灞呭ICU'),
+(4, '鍖荤枟璁惧', '4', 'task_additional_fee_type', '', 'default', 'N', '0', 'admin', NOW(), '鍖荤枟璁惧');
diff --git a/sql/update_task_payment_20250123.sql b/sql/update_task_payment_20250123.sql
new file mode 100644
index 0000000..ea08740
--- /dev/null
+++ b/sql/update_task_payment_20250123.sql
@@ -0,0 +1,8 @@
+-- 淇敼sys_task_payment琛紝璋冩暣浜岀淮鐮佺浉鍏冲瓧娈�
+-- 1. 淇敼code_url瀛楁涓篢EXT绫诲瀷锛岀敤浜庡瓨鍌˙ase64浜岀淮鐮佸浘鐗�
+ALTER TABLE sys_task_payment MODIFY COLUMN code_url TEXT COMMENT 'Base64缂栫爜鐨勪簩缁寸爜鍥剧墖';
+
+-- 濡傛灉浠ュ悗闇�瑕佸尯鍒哢RL鍜孊ase64锛屽彲浠ヨ�冭檻鎷嗗垎涓轰袱涓瓧娈碉細
+-- ALTER TABLE sys_task_payment
+-- MODIFY COLUMN code_url VARCHAR(500) COMMENT '浜岀淮鐮佸師濮嬮摼鎺RL',
+-- ADD COLUMN qr_base64 LONGTEXT COMMENT 'Base64缂栫爜鐨勪簩缁寸爜鍥剧墖' AFTER code_url;
--
Gitblit v1.9.1