| | |
| | | <view class="staff-item" v-for="(staff, index) in selectedStaff" :key="staff.userId"> |
| | | <view class="staff-info"> |
| | | <text class="staff-name">{{ staff.nickName }}</text> |
| | | <view class="staff-roles"> |
| | | <text class="staff-role" v-for="(roleType, idx) in staff.types" :key="idx"> |
| | | {{ getUserTypeName(roleType) }} |
| | | </text> |
| | | </view> |
| | | </view> |
| | | <uni-icons |
| | | v-if="canRemove(index)" |
| | |
| | | <view class="staff-filter"> |
| | | <view |
| | | class="filter-item" |
| | | :class="{ active: staffFilterType === 'all' }" |
| | | @click="filterStaff('all')" |
| | | >全部</view> |
| | | <view |
| | | class="filter-item" |
| | | :class="{ active: staffFilterType === 'driver' }" |
| | | @click="filterStaff('driver')" |
| | | >司机</view> |
| | |
| | | </view> |
| | | <view class="staff-detail-row"> |
| | | <text class="staff-dept">{{ staff.deptName }}</text> |
| | | <view class="staff-types"> |
| | | <text |
| | | class="type-tag" |
| | | :class="'type-' + type" |
| | | v-for="(type, idx) in staff.types" |
| | | :key="idx" |
| | | > |
| | | {{ getUserTypeName(type) }} |
| | | </text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <uni-icons |
| | |
| | | allStaffList: [], |
| | | filteredStaffList: [], |
| | | staffSearchKeyword: '', |
| | | staffFilterType: 'all' |
| | | staffFilterType: 'driver' // 默认选中司机 |
| | | } |
| | | }, |
| | | computed: { |
| | |
| | | closeStaffSelector() { |
| | | this.$refs.staffPopup.close() |
| | | this.staffSearchKeyword = '' |
| | | this.staffFilterType = 'all' |
| | | this.staffFilterType = 'driver' // 重置为默认司机 |
| | | }, |
| | | |
| | | // 人员搜索 |
| | |
| | | filterStaffList() { |
| | | let list = [...this.allStaffList] |
| | | |
| | | // 按类型过滤(支持多类型) |
| | | if (this.staffFilterType !== 'all') { |
| | | list = list.filter(staff => staff.types.includes(this.staffFilterType)) |
| | | } |
| | | |
| | | // 按关键词搜索 |
| | | if (this.staffSearchKeyword && this.staffSearchKeyword.trim() !== '') { |
| | | const keyword = this.staffSearchKeyword.trim().toLowerCase() |
| | |
| | | (staff.phonenumber && staff.phonenumber.includes(keyword)) |
| | | }) |
| | | } |
| | | |
| | | // 根据选中的类型,将有该身份的人排在前面 |
| | | list.sort((a, b) => { |
| | | const aHasType = a.types.includes(this.staffFilterType) |
| | | const bHasType = b.types.includes(this.staffFilterType) |
| | | |
| | | if (aHasType && !bHasType) return -1 // a有该身份,排前面 |
| | | if (!aHasType && bHasType) return 1 // b有该身份,排前面 |
| | | return 0 // 都有或都没有,保持原顺序 |
| | | }) |
| | | |
| | | this.filteredStaffList = list |
| | | }, |
| | |
| | | this.selectedStaff.splice(index, 1) |
| | | } else { |
| | | // 未选中,添加 |
| | | this.selectedStaff.push(staff) |
| | | this.selectedStaff.push({ ...staff }) |
| | | } |
| | | }, |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | // 人员选择弹窗样式 |
| | | .staff-selector-popup { |
| | | background-color: white; |
| | | border-radius: 20rpx 20rpx 0 0; |
| | | max-height: 80vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 30rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | flex-shrink: 0; |
| | | |
| | | .popup-title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .popup-close { |
| | | padding: 10rpx; |
| | | } |
| | | } |
| | | |
| | | .search-box { |
| | | display: flex; |
| | | align-items: center; |
| | | margin: 20rpx 30rpx; |
| | | padding: 15rpx 20rpx; |
| | | background-color: #f5f5f5; |
| | | border-radius: 10rpx; |
| | | flex-shrink: 0; |
| | | |
| | | .search-input { |
| | | flex: 1; |
| | | margin-left: 10rpx; |
| | | font-size: 28rpx; |
| | | } |
| | | } |
| | | |
| | | .staff-filter { |
| | | display: flex; |
| | | padding: 0 30rpx 20rpx; |
| | | gap: 20rpx; |
| | | flex-shrink: 0; |
| | | |
| | | .filter-item { |
| | | flex: 1; |
| | | text-align: center; |
| | | padding: 15rpx 0; |
| | | background-color: #f5f5f5; |
| | | border-radius: 10rpx; |
| | | font-size: 28rpx; |
| | | color: #666; |
| | | |
| | | &.active { |
| | | background-color: #007AFF; |
| | | color: white; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .staff-list-popup { |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | padding: 0 30rpx; |
| | | |
| | | .staff-item-popup { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 25rpx 20rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | |
| | | &:active { |
| | | background-color: #f5f5f5; |
| | | } |
| | | |
| | | .staff-info { |
| | | flex: 1; |
| | | |
| | | .staff-name-row { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 10rpx; |
| | | |
| | | .staff-name { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | margin-right: 20rpx; |
| | | } |
| | | |
| | | .staff-phone { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | } |
| | | |
| | | .staff-detail-row { |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .staff-dept { |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | margin-right: 20rpx; |
| | | } |
| | | |
| | | .staff-post { |
| | | font-size: 24rpx; |
| | | color: #007AFF; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .checkbox-empty { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | border: 2rpx solid #ddd; |
| | | border-radius: 50%; |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | text-align: center; |
| | | padding: 100rpx 0; |
| | | color: #999; |
| | | |
| | | text { |
| | | display: block; |
| | | margin-top: 20rpx; |
| | | font-size: 28rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .popup-footer { |
| | | display: flex; |
| | | padding: 20rpx 30rpx; |
| | | border-top: 1rpx solid #f0f0f0; |
| | | gap: 20rpx; |
| | | flex-shrink: 0; |
| | | |
| | | button { |
| | | flex: 1; |
| | | height: 80rpx; |
| | | border-radius: 10rpx; |
| | | font-size: 30rpx; |
| | | } |
| | | |
| | | .cancel-btn { |
| | | background-color: #f5f5f5; |
| | | color: #666; |
| | | } |
| | | |
| | | .confirm-btn { |
| | | background-color: #007AFF; |
| | | color: white; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 智能识别弹窗样式 |
| | | .smart-parse-popup { |
| | | background-color: white; |
| | |
| | | /> |
| | | </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> |
| | | <StaffSelector |
| | | v-model="selectedStaff" |
| | | label="执行任务人员" |
| | | :required="false" |
| | | :auto-add-current-user="false" |
| | | :current-user-removable="true" |
| | | @change="onStaffChange" |
| | | /> |
| | | |
| | | <view class="form-section-title">患者信息</view> |
| | | <view class="form-item"> |
| | |
| | | ></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> |
| | | |
| | |
| | | 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' |
| | |
| | | import HospitalSelector from './components/HospitalSelector.vue' |
| | | import DiseaseSelector from './components/DiseaseSelector.vue' |
| | | import DepartureSelector from './components/DepartureSelector.vue' |
| | | import StaffSelector from './components/StaffSelector.vue' |
| | | import distanceCalculator from '@/mixins/distanceCalculator.js' |
| | | |
| | | export default { |
| | |
| | | OrganizationSelector, |
| | | HospitalSelector, |
| | | DiseaseSelector, |
| | | DepartureSelector |
| | | DepartureSelector, |
| | | StaffSelector |
| | | }, |
| | | mixins: [distanceCalculator], |
| | | data() { |
| | |
| | | departureLatitude: null, |
| | | selectedDiseases: [], // 已选择的病情列表(确保初始化为空数组) |
| | | selectedStaff: [], // 已选择的人员列表(确保初始化为空数组) |
| | | allStaffList: [], // 所有人员列表 |
| | | filteredStaffList: [], // 过滤后的人员列表 |
| | | staffSearchKeyword: '', // 人员搜索关键词 |
| | | staffFilterType: 'driver', // 人员筛选类型 |
| | | taskForm: { |
| | | transferTime: '', |
| | | patient: { |
| | |
| | | if (options.id) { |
| | | this.taskId = options.id |
| | | this.loadTaskDetail() |
| | | this.loadDeptStaff() // 加载人员列表 |
| | | } else { |
| | | this.$modal.showToast('任务ID不能为空') |
| | | setTimeout(() => { |
| | |
| | | // 组件已经通过 v-model 更新了 selectedDiseases |
| | | }, |
| | | |
| | | // 加载当前用户所在分公司的所有人员 |
| | | loadDeptStaff() { |
| | | console.log('开始加载人员列表') |
| | | |
| | | listBranchUsers().then(response => { |
| | | console.log('人员列表API响应:', response) |
| | | const userList = response.data || [] |
| | | console.log('解析出的用户列表:', 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) |
| | | // 人员变化 |
| | | onStaffChange(staff) { |
| | | console.log('选中人员变化:', staff) |
| | | // 组件已经通过 v-model 更新了 selectedStaff |
| | | }, |
| | | |
| | | // 解析病情信息(从字符串解析出ICD-10疾病列表)- 修复:与创建界面保持一致 |
| | |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | } |
| | | } |
| | |
| | | // 通信成功且业务成功 |
| | | if ("SUCCESS".equals(returnCode) && "SUCCESS".equals(resultCode)) { |
| | | // 查询订单和交易(使用业务订单号查询) |
| | | PaymentOrder order = paymentOrderMapper.selectByBizOrderIdAndChannel(outTradeNo, "WECHAT"); |
| | | PaymentOrder order = paymentOrderMapper.selectById(Long.parseLong(outTradeNo)); |
| | | if (order == null) { |
| | | log.error("订单不存在: {}", outTradeNo); |
| | | updateNotifyLog(notifyLog.getId(), false, "订单不存在"); |
| | |
| | | // 交易成功 |
| | | if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) { |
| | | // 查询订单和交易(使用业务订单号查询) |
| | | PaymentOrder order = paymentOrderMapper.selectByBizOrderIdAndChannel(outTradeNo, "ALIPAY"); |
| | | PaymentOrder order = paymentOrderMapper.selectById(Long.parseLong(outTradeNo)); |
| | | if (order == null) { |
| | | log.error("订单不存在: {}", outTradeNo); |
| | | updateNotifyLog(notifyLog.getId(), false, "订单不存在"); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 处理第三方支付宝回调 |
| | | * 第三方支付宝通过GET方式回调,参数通过URL传递 |
| | | * |
| | | * 参数格式: |
| | | * method: 方法名 |
| | | * APPID: 应用ID |
| | | * PaidMoneyType: 支付类型 |
| | | * PaidMoney: 支付金额(分) |
| | | * PaidRemarks: 支付备注(包含transaction_id) |
| | | * UnixTime: 时间戳 |
| | | * Sign: 签名 |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void processAlipayThirdPartyNotify(Map<String, String> params) { |
| | | String method = params.get("method"); // 方法名 |
| | | String appId = params.get("APPID"); // 应用ID |
| | | String paidMoneyType = params.get("PaidMoneyType"); // 支付类型 |
| | | String paidMoney = params.get("PaidMoney"); // 支付金额(分) |
| | | String paidRemarks = params.get("PaidRemarks"); // 支付备注(可能包含订单号和交易号) |
| | | String unixTime = params.get("UnixTime"); // 时间戳 |
| | | String sign = params.get("Sign"); // 签名 |
| | | |
| | | log.info("处理第三方支付宝回调,method: {}, APPID: {}, PaidMoneyType: {}, PaidMoney: {}, PaidRemarks: {}, UnixTime: {}, Sign: {}", |
| | | method, appId, paidMoneyType, paidMoney, paidRemarks, unixTime, sign); |
| | | log.info("第三方支付宝回调完整参数: {}", params); |
| | | |
| | | // 从PaidRemarks中提取订单号(out_trade_no) |
| | | // PaidRemarks格式可能是: "订单号+交易号" 或其他格式,需要解析 |
| | | String outTradeNo = extractOutTradeNo(paidRemarks); |
| | | String transactionId = extractTransactionId(paidRemarks); |
| | | |
| | | if (outTradeNo == null || outTradeNo.isEmpty()) { |
| | | log.error("无法从PaidRemarks中提取订单号: {}", paidRemarks); |
| | | return; |
| | | } |
| | | |
| | | log.info("解析出订单号: {}, 交易号: {}", outTradeNo, transactionId); |
| | | |
| | | // 检查幂等性(使用订单号或签名) |
| | | String notifyId = transactionId != null ? transactionId : sign; |
| | | if (isNotifyProcessed("ALIPAY_THIRD_PARTY", notifyId)) { |
| | | log.info("第三方支付宝回调已处理过,标识: {}", notifyId); |
| | | return; |
| | | } |
| | | |
| | | // 记录回调日志 |
| | | NotifyLog notifyLog = saveNotifyLog("ALIPAY_THIRD_PARTY", notifyId, JSON.toJSONString(params), true); |
| | | |
| | | try { |
| | | // 第三方支付宝成功的判断条件(根据实际情况调整) |
| | | // 通常只要能收到回调就表示支付成功 |
| | | if (paidMoney != null && Integer.parseInt(paidMoney) > 0) { |
| | | // 查询订单(使用商户订单号,这里的outTradeNo实际是我们的订单ID) |
| | | PaymentOrder order = paymentOrderMapper.selectById(Long.valueOf(outTradeNo)); |
| | | if (order == null) { |
| | | log.error("订单不存在: {}", outTradeNo); |
| | | updateNotifyLog(notifyLog.getId(), false, "订单不存在"); |
| | | return; |
| | | } |
| | | |
| | | // 验证金额是否匹配 |
| | | if (!order.getAmount().equals(Integer.parseInt(paidMoney))) { |
| | | log.error("订单金额不匹配,订单金额: {}, 回调金额: {}", order.getAmount(), paidMoney); |
| | | updateNotifyLog(notifyLog.getId(), false, "金额不匹配"); |
| | | return; |
| | | } |
| | | |
| | | // 查询待支付交易 |
| | | PaymentTransaction transaction = paymentTransactionMapper.selectPendingByOrderId(order.getId()); |
| | | if (transaction == null) { |
| | | // 如果没有待支付交易,尝试查询最新交易 |
| | | transaction = paymentTransactionMapper.selectLatestByOrderId(order.getId()); |
| | | if (transaction == null || "SUCCEEDED".equals(transaction.getStatus())) { |
| | | log.warn("交易已完成或不存在,订单ID: {}", order.getId()); |
| | | updateNotifyLog(notifyLog.getId(), true, "交易已完成"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // 更新交易状态 |
| | | transaction.setStatus("SUCCEEDED"); |
| | | if (transactionId != null) { |
| | | transaction.setChannelTradeNo(transactionId); |
| | | } |
| | | transaction.setPaidAt(LocalDateTime.now()); |
| | | transaction.setResponseSnapshot(JSON.toJSONString(params)); |
| | | paymentTransactionMapper.update(transaction); |
| | | |
| | | // 更新订单状态 |
| | | order.setStatus("SUCCEEDED"); |
| | | if (transactionId != null) { |
| | | 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("第三方支付宝回调处理成功,订单ID: {}, 交易ID: {}", order.getId(), transaction.getId()); |
| | | } else { |
| | | log.warn("第三方支付宝支付金额异常: {}", paidMoney); |
| | | updateNotifyLog(notifyLog.getId(), true, "金额异常: " + paidMoney); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("处理第三方支付宝回调异常", e); |
| | | updateNotifyLog(notifyLog.getId(), false, "处理异常: " + e.getMessage()); |
| | | throw e; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从PaidRemarks中提取订单号 |
| | | * PaidRemarks可能包含订单号和交易号,需要解析 |
| | | */ |
| | | private String extractOutTradeNo(String paidRemarks) { |
| | | if (paidRemarks == null || paidRemarks.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | // 假设PaidRemarks格式: "订单号+交易号" 或 "订单号" |
| | | // 根据实际格式调整解析逻辑 |
| | | // 这里假设订单号在最前面,可能用某个分隔符分隔 |
| | | |
| | | // 尝试提取纯数字作为订单ID |
| | | String[] parts = paidRemarks.split("[^0-9]+"); |
| | | for (String part : parts) { |
| | | if (part.length() > 10) { // 订单ID通常是长整型,长度较长 |
| | | return part; |
| | | } |
| | | } |
| | | |
| | | // 如果没有找到,返回整个字符串的数字部分 |
| | | return paidRemarks.replaceAll("[^0-9]", ""); |
| | | } |
| | | |
| | | /** |
| | | * 从PaidRemarks中提取交易号 |
| | | */ |
| | | private String extractTransactionId(String paidRemarks) { |
| | | if (paidRemarks == null || paidRemarks.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | // 如果PaidRemarks包含支付宝交易号(通常以2开头的28位数字) |
| | | // 这里简单返回null,可以根据实际格式调整 |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 处理第三方微信回调 |
| | | * 第三方微信通过GET方式回调,参数通过URL传递 |
| | | * |
| | | * 参数格式: |
| | | * method: 方法名 |
| | | * APPID: 应用ID |
| | | * PaidMoneyType: 支付类型 |
| | | * PaidMoney: 支付金额(分) |
| | | * PaidRemarks: 支付备注(包含transaction_id) |
| | | * UnixTime: 时间戳 |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void processWechatThirdPartyNotify(Map<String, String> params) { |
| | | String method = params.get("method"); // 方法名 |
| | | String appId = params.get("APPID"); // 应用ID |
| | | String paidMoneyType = params.get("PaidMoneyType"); // 支付类型 |
| | | String paidMoney = params.get("PaidMoney"); // 支付金额(分) |
| | | String paidRemarks = params.get("PaidRemarks"); // 支付备注(可能包含订单号和交易号) |
| | | String unixTime = params.get("UnixTime"); // 时间戳 |
| | | |
| | | log.info("处理第三方微信回调,method: {}, APPID: {}, PaidMoneyType: {}, PaidMoney: {}, PaidRemarks: {}, UnixTime: {}", |
| | | method, appId, paidMoneyType, paidMoney, paidRemarks, unixTime); |
| | | log.info("第三方微信回调完整参数: {}", params); |
| | | |
| | | // 从PaidRemarks中提取订单号(out_trade_no) |
| | | String outTradeNo = extractOutTradeNo(paidRemarks); |
| | | String transactionId = extractTransactionId(paidRemarks); |
| | | |
| | | if (outTradeNo == null || outTradeNo.isEmpty()) { |
| | | log.error("无法从PaidRemarks中提取订单号: {}", paidRemarks); |
| | | return; |
| | | } |
| | | |
| | | log.info("解析出订单号: {}, 交易号: {}", outTradeNo, transactionId); |
| | | |
| | | // 检查幂等性(使用订单号或时间戳) |
| | | String notifyId = transactionId != null ? transactionId : (outTradeNo + "_" + unixTime); |
| | | if (isNotifyProcessed("WECHAT_THIRD_PARTY", notifyId)) { |
| | | log.info("第三方微信回调已处理过,标识: {}", notifyId); |
| | | return; |
| | | } |
| | | |
| | | // 记录回调日志 |
| | | NotifyLog notifyLog = saveNotifyLog("WECHAT_THIRD_PARTY", notifyId, JSON.toJSONString(params), true); |
| | | |
| | | try { |
| | | // 第三方微信成功的判断条件(通常只要能收到回调就表示支付成功) |
| | | if (paidMoney != null && Integer.parseInt(paidMoney) > 0) { |
| | | // 查询订单(使用商户订单号,这里的outTradeNo实际是我们的订单ID) |
| | | PaymentOrder order = paymentOrderMapper.selectById(Long.valueOf(outTradeNo)); |
| | | if (order == null) { |
| | | log.error("订单不存在: {}", outTradeNo); |
| | | updateNotifyLog(notifyLog.getId(), false, "订单不存在"); |
| | | return; |
| | | } |
| | | |
| | | // 验证金额是否匹配 |
| | | if (!order.getAmount().equals(Integer.parseInt(paidMoney))) { |
| | | log.error("订单金额不匹配,订单金额: {}, 回调金额: {}", order.getAmount(), paidMoney); |
| | | updateNotifyLog(notifyLog.getId(), false, "金额不匹配"); |
| | | return; |
| | | } |
| | | |
| | | // 查询待支付交易 |
| | | PaymentTransaction transaction = paymentTransactionMapper.selectPendingByOrderId(order.getId()); |
| | | if (transaction == null) { |
| | | // 如果没有待支付交易,尝试查询最新交易 |
| | | transaction = paymentTransactionMapper.selectLatestByOrderId(order.getId()); |
| | | if (transaction == null || "SUCCEEDED".equals(transaction.getStatus())) { |
| | | log.warn("交易已完成或不存在,订单ID: {}", order.getId()); |
| | | updateNotifyLog(notifyLog.getId(), true, "交易已完成"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // 更新交易状态 |
| | | transaction.setStatus("SUCCEEDED"); |
| | | if (transactionId != null) { |
| | | transaction.setChannelTradeNo(transactionId); |
| | | } |
| | | transaction.setPaidAt(LocalDateTime.now()); |
| | | transaction.setResponseSnapshot(JSON.toJSONString(params)); |
| | | paymentTransactionMapper.update(transaction); |
| | | |
| | | // 更新订单状态 |
| | | order.setStatus("SUCCEEDED"); |
| | | if (transactionId != null) { |
| | | 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("第三方微信回调处理成功,订单ID: {}, 交易ID: {}", order.getId(), transaction.getId()); |
| | | } else { |
| | | log.warn("第三方微信支付金额异常: {}", paidMoney); |
| | | updateNotifyLog(notifyLog.getId(), true, "金额异常: " + paidMoney); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("处理第三方微信回调异常", e); |
| | | updateNotifyLog(notifyLog.getId(), false, "处理异常: " + e.getMessage()); |
| | | throw e; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 检查通知是否已处理(幂等性) |
| | | */ |
| | | private boolean isNotifyProcessed(String channel, String notifyId) { |
| | |
| | | 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.config.WechatPayConfig; |
| | | import com.ruoyi.payment.infrastructure.persistence.mapper.PaymentOrderMapper; |
| | | import com.ruoyi.payment.infrastructure.persistence.mapper.PaymentTransactionMapper; |
| | | import com.ruoyi.payment.infrastructure.util.QrCodeUtil; |
| | |
| | | @Autowired |
| | | private AlipayConfig alipayConfig; |
| | | |
| | | @Autowired |
| | | private WechatPayConfig wechatPayConfig; |
| | | |
| | | /** |
| | | * 发起微信Native支付 |
| | | */ |
| | |
| | | requestParams.put("bizOrderId", request.getBizOrderId()); |
| | | requestParams.put("amount", request.getAmount()); |
| | | requestParams.put("subject", request.getSubject()); |
| | | requestParams.put("notifyUrl",wechatPayConfig.getNotifyUrl()); |
| | | transaction.setRequestParams(JSON.toJSONString(requestParams)); |
| | | |
| | | paymentTransactionMapper.insert(transaction); |
| | |
| | | requestParams.put("bizOrderId", request.getBizOrderId()); |
| | | requestParams.put("amount", request.getAmount()); |
| | | requestParams.put("subject", request.getSubject()); |
| | | requestParams.put("notifyUrl",alipayConfig.getNotifyUrl()); |
| | | transaction.setRequestParams(JSON.toJSONString(requestParams)); |
| | | |
| | | paymentTransactionMapper.insert(transaction); |
| | |
| | | requestParams.put("amount", request.getAmount()); |
| | | requestParams.put("subject", request.getSubject()); |
| | | requestParams.put("thirdParty", true); |
| | | requestParams.put("notifyUrl",alipayConfig.getThirdParty().getDefaultNotifyUrl()); |
| | | transaction.setRequestParams(JSON.toJSONString(requestParams)); |
| | | |
| | | paymentTransactionMapper.insert(transaction); |
| | |
| | | private String callAlipayThirdPartyPrecreate(PaymentOrder order, PaymentRequest request) { |
| | | try { |
| | | // 使用AlipayConfig中配置的回调地址 |
| | | String notifyUrl = alipayConfig.getNotifyUrl(); |
| | | String notifyUrl = alipayConfig.getThirdParty().getDefaultNotifyUrl(); |
| | | 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); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 轮询查询支付状态(用于回调失败的补偿机制) |
| | | * 支持微信和支付宝 |
| | | * |
| | | * @param orderId 订单ID |
| | | * @return 轮询结果(包含订单状态和交易状态) |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Map<String, Object> pollPaymentStatus(Long orderId) { |
| | | log.info("开始轮询查询支付状态,订单ID: {}", orderId); |
| | | |
| | | Map<String, Object> result = new HashMap<>(); |
| | | |
| | | // 1. 查询订单信息 |
| | | PaymentOrder order = paymentOrderMapper.selectById(orderId); |
| | | if (order == null) { |
| | | log.error("订单不存在,订单ID: {}", orderId); |
| | | throw new RuntimeException("订单不存在"); |
| | | } |
| | | |
| | | // 如果订单已经是成功状态,直接返回 |
| | | if (OrderStatus.SUCCEEDED.getCode().equals(order.getStatus())) { |
| | | log.info("订单已成功,无需轮询,订单ID: {}", orderId); |
| | | result.put("orderStatus", order.getStatus()); |
| | | result.put("needPoll", false); |
| | | result.put("message", "订单已成功"); |
| | | return result; |
| | | } |
| | | |
| | | // 2. 查询最新交易记录 |
| | | PaymentTransaction transaction = paymentTransactionMapper.selectLatestByOrderId(orderId); |
| | | if (transaction == null) { |
| | | log.error("未找到交易记录,订单ID: {}", orderId); |
| | | throw new RuntimeException("未找到交易记录"); |
| | | } |
| | | |
| | | // 如果交易已成功,但订单状态未更新,更新订单状态 |
| | | if (TransactionStatus.SUCCEEDED.getCode().equals(transaction.getStatus())) { |
| | | log.info("交易已成功,同步订单状态,订单ID: {}", orderId); |
| | | order.setStatus(OrderStatus.SUCCEEDED.getCode()); |
| | | order.setUpdatedAt(LocalDateTime.now()); |
| | | paymentOrderMapper.update(order); |
| | | |
| | | result.put("orderStatus", OrderStatus.SUCCEEDED.getCode()); |
| | | result.put("transactionStatus", transaction.getStatus()); |
| | | result.put("needPoll", false); |
| | | result.put("message", "支付成功"); |
| | | return result; |
| | | } |
| | | |
| | | // 3. 根据支付渠道调用相应的查询接口 |
| | | String channel = order.getChannel(); |
| | | boolean paymentSuccess = false; |
| | | String tradeStatus = null; |
| | | |
| | | try { |
| | | if (PayChannel.WECHAT.getCode().equals(channel)) { |
| | | // 查询微信支付状态 |
| | | paymentSuccess = queryWechatPaymentStatus(order, transaction); |
| | | tradeStatus = paymentSuccess ? "SUCCESS" : "NOTPAY"; |
| | | result.put("paymethod","WECHAT"); |
| | | } else if (PayChannel.ALIPAY.getCode().equals(channel)) { |
| | | // 判断是否为第三方支付宝 |
| | | boolean isThirdParty = isThirdPartyAlipay(transaction); |
| | | result.put("paymethod","ALIPAY"); |
| | | if (isThirdParty) { |
| | | // 查询第三方支付宝状态 |
| | | tradeStatus = queryAlipayThirdPartyTradeStatus(orderId); |
| | | paymentSuccess = "SUCCESS".equals(tradeStatus); |
| | | } else { |
| | | // 查询官方支付宝状态 |
| | | paymentSuccess = queryAlipayPaymentStatus(order, transaction); |
| | | tradeStatus = paymentSuccess ? "TRADE_SUCCESS" : "WAIT_BUYER_PAY"; |
| | | } |
| | | } else { |
| | | log.error("不支持的支付渠道: {}", channel); |
| | | throw new RuntimeException("不支持的支付渠道"); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("查询支付状态异常,订单ID: {}", orderId, e); |
| | | result.put("orderStatus", order.getStatus()); |
| | | result.put("transactionStatus", transaction.getStatus()); |
| | | result.put("needPoll", true); |
| | | result.put("error", e.getMessage()); |
| | | result.put("message", "查询异常,请稍后重试"); |
| | | return result; |
| | | } |
| | | |
| | | // 4. 如果支付成功,更新订单和交易状态 |
| | | if (paymentSuccess) { |
| | | log.info("轮询查询发现支付成功,订单ID: {}, 交易状态: {}", orderId, tradeStatus); |
| | | |
| | | // 更新交易状态 |
| | | transaction.setStatus(TransactionStatus.SUCCEEDED.getCode()); |
| | | transaction.setPaidAt(LocalDateTime.now()); |
| | | paymentTransactionMapper.update(transaction); |
| | | |
| | | // 更新订单状态 |
| | | order.setStatus(OrderStatus.SUCCEEDED.getCode()); |
| | | order.setUpdatedAt(LocalDateTime.now()); |
| | | paymentOrderMapper.update(order); |
| | | |
| | | result.put("orderStatus", OrderStatus.SUCCEEDED.getCode()); |
| | | result.put("transactionStatus", TransactionStatus.SUCCEEDED.getCode()); |
| | | result.put("tradeStatus", tradeStatus); |
| | | result.put("needPoll", false); |
| | | result.put("message", "支付成功"); |
| | | |
| | | log.info("轮询查询处理完成,订单和交易状态已更新,订单ID: {}", orderId); |
| | | } else { |
| | | log.info("轮询查询,支付尚未完成,订单ID: {}, 交易状态: {}", orderId, tradeStatus); |
| | | result.put("orderStatus", order.getStatus()); |
| | | result.put("transactionStatus", transaction.getStatus()); |
| | | result.put("tradeStatus", tradeStatus); |
| | | result.put("needPoll", true); |
| | | result.put("message", "支付尚未完成,请继续轮询"); |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 查询微信支付状态 |
| | | */ |
| | | private boolean queryWechatPaymentStatus(PaymentOrder order, PaymentTransaction transaction) throws Exception { |
| | | log.info("查询微信支付状态,订单ID: {}", order.getId()); |
| | | |
| | | String outTradeNo = String.valueOf(order.getId()); |
| | | Map<String, String> queryResult = wxPayV2Client.queryOrder(outTradeNo); |
| | | |
| | | // 检查业务结果 |
| | | if (!"SUCCESS".equals(queryResult.get("result_code"))) { |
| | | log.warn("微信订单查询业务失败: {}", queryResult.get("err_code_des")); |
| | | return false; |
| | | } |
| | | |
| | | // 检查交易状态 |
| | | String tradeState = queryResult.get("trade_state"); |
| | | log.info("微信订单状态: {}", tradeState); |
| | | |
| | | return "SUCCESS".equals(tradeState); |
| | | } |
| | | |
| | | /** |
| | | * 查询支付宝支付状态(官方接口) |
| | | */ |
| | | private boolean queryAlipayPaymentStatus(PaymentOrder order, PaymentTransaction transaction) throws Exception { |
| | | log.info("查询支付宝支付状态,订单ID: {}", order.getId()); |
| | | |
| | | String outTradeNo = String.valueOf(order.getId()); |
| | | com.alipay.api.response.AlipayTradeQueryResponse queryResult = alipayF2FClient.queryOrder(outTradeNo); |
| | | |
| | | // 检查交易状态 |
| | | String tradeStatus = queryResult.getTradeStatus(); |
| | | log.info("支付宝订单状态: {}", tradeStatus); |
| | | log.info("支付宝订单查询结果: {}", queryResult.getBody()); |
| | | // TRADE_SUCCESS 或 TRADE_FINISHED 表示支付成功 |
| | | return "TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus); |
| | | } |
| | | |
| | | /** |
| | | * 判断是否为第三方支付宝 |
| | | */ |
| | | private boolean isThirdPartyAlipay(PaymentTransaction transaction) { |
| | | if (transaction.getRequestParams() == null) { |
| | | return false; |
| | | } |
| | | try { |
| | | Map<String, Object> params = JSON.parseObject(transaction.getRequestParams(), Map.class); |
| | | return params.containsKey("thirdParty") && Boolean.TRUE.equals(params.get("thirdParty")); |
| | | } catch (Exception e) { |
| | | log.warn("解析交易请求参数失败", e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据交易单号查询微信支付状态 |
| | | * |
| | | * @param outTradeNo 商户订单号(交易单号) |
| | | * @return 交易状态信息 |
| | | */ |
| | | public Map<String, Object> queryWechatTradeByOutTradeNo(String outTradeNo) { |
| | | log.info("根据交易单号查询微信支付状态,商户订单号: {}", outTradeNo); |
| | | |
| | | Map<String, Object> result = new HashMap<>(); |
| | | |
| | | try { |
| | | Map<String, String> queryResult = wxPayV2Client.queryOrder(outTradeNo); |
| | | |
| | | // 检查调用是否成功 |
| | | if (!"SUCCESS".equals(queryResult.get("return_code"))) { |
| | | result.put("success", false); |
| | | result.put("message", "微信查询接口调用失败: " + queryResult.get("return_msg")); |
| | | return result; |
| | | } |
| | | |
| | | // 检查业务结果 |
| | | if (!"SUCCESS".equals(queryResult.get("result_code"))) { |
| | | result.put("success", false); |
| | | result.put("message", "微信订单查询业务失败: " + queryResult.get("err_code_des")); |
| | | result.put("errorCode", queryResult.get("err_code")); |
| | | return result; |
| | | } |
| | | |
| | | // 返回交易信息 |
| | | result.put("success", true); |
| | | result.put("tradeState", queryResult.get("trade_state")); // 交易状态 |
| | | result.put("tradeStateDesc", queryResult.get("trade_state_desc")); // 交易状态描述 |
| | | result.put("outTradeNo", queryResult.get("out_trade_no")); // 商户订单号 |
| | | result.put("transactionId", queryResult.get("transaction_id")); // 微信支付订单号 |
| | | result.put("totalFee", queryResult.get("total_fee")); // 订单金额(分) |
| | | result.put("timeEnd", queryResult.get("time_end")); // 支付完成时间 |
| | | result.put("openid", queryResult.get("openid")); // 用户标识 |
| | | |
| | | // 判断是否支付成功 |
| | | boolean isPaid = "SUCCESS".equals(queryResult.get("trade_state")); |
| | | result.put("isPaid", isPaid); |
| | | result.put("message", isPaid ? "支付成功" : "支付未完成"); |
| | | |
| | | log.info("微信交易查询成功,交易状态: {}", queryResult.get("trade_state")); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("查询微信交易状态异常,商户订单号: {}", outTradeNo, e); |
| | | result.put("success", false); |
| | | result.put("message", "查询异常: " + e.getMessage()); |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 根据交易单号查询支付宝支付状态 |
| | | * |
| | | * @param outTradeNo 商户订单号(交易单号) |
| | | * @return 交易状态信息 |
| | | */ |
| | | public Map<String, Object> queryAlipayTradeByOutTradeNo(String outTradeNo) { |
| | | log.info("根据交易单号查询支付宝支付状态,商户订单号: {}", outTradeNo); |
| | | |
| | | Map<String, Object> result = new HashMap<>(); |
| | | |
| | | try { |
| | | com.alipay.api.response.AlipayTradeQueryResponse queryResult = alipayF2FClient.queryOrder(outTradeNo); |
| | | |
| | | // 检查调用是否成功 |
| | | if (!queryResult.isSuccess()) { |
| | | result.put("success", false); |
| | | result.put("message", "支付宝查询失败: " + queryResult.getSubMsg()); |
| | | result.put("errorCode", queryResult.getSubCode()); |
| | | return result; |
| | | } |
| | | |
| | | // 返回交易信息 |
| | | result.put("success", true); |
| | | result.put("tradeStatus", queryResult.getTradeStatus()); // 交易状态 |
| | | result.put("outTradeNo", queryResult.getOutTradeNo()); // 商户订单号 |
| | | result.put("tradeNo", queryResult.getTradeNo()); // 支付宝交易号 |
| | | result.put("totalAmount", queryResult.getTotalAmount()); // 订单金额(元) |
| | | result.put("buyerPayAmount", queryResult.getBuyerPayAmount()); // 买家实际支付金额 |
| | | result.put("sendPayDate", queryResult.getSendPayDate()); // 交易支付时间 |
| | | result.put("buyerLogonId", queryResult.getBuyerLogonId()); // 买家支付宝账号 |
| | | |
| | | // 判断是否支付成功 |
| | | String tradeStatus = queryResult.getTradeStatus(); |
| | | boolean isPaid = "TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus); |
| | | result.put("isPaid", isPaid); |
| | | |
| | | // 状态说明 |
| | | String message; |
| | | if ("TRADE_SUCCESS".equals(tradeStatus)) { |
| | | message = "交易支付成功"; |
| | | } else if ("TRADE_FINISHED".equals(tradeStatus)) { |
| | | message = "交易结束,不可退款"; |
| | | } else if ("WAIT_BUYER_PAY".equals(tradeStatus)) { |
| | | message = "交易创建,等待买家付款"; |
| | | } else if ("TRADE_CLOSED".equals(tradeStatus)) { |
| | | message = "未付款交易超时关闭,或支付完成后全额退款"; |
| | | } else { |
| | | message = "未知状态: " + tradeStatus; |
| | | } |
| | | result.put("message", message); |
| | | |
| | | log.info("支付宝交易查询成功,交易状态: {}", tradeStatus); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("查询支付宝交易状态异常,商户订单号: {}", outTradeNo, e); |
| | | result.put("success", false); |
| | | result.put("message", "查询异常: " + e.getMessage()); |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | } |
| | |
| | | /** 第三方接口URL */ |
| | | 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 String defaultNotifyUrl = "https://api.966120.com.cn/alipay/DspNotifyUrl.php"; |
| | | /** 超时时间(毫秒) */ |
| | | private int timeout = 30000; |
| | | } |
| New file |
| | |
| | | package com.ruoyi.payment.infrastructure.config; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.io.Serializable; |
| | | |
| | | @Data |
| | | public class ThirdPartyConfig implements Serializable { |
| | | /** 是否启用 */ |
| | | private boolean enabled = false; |
| | | /** 第三方接口URL */ |
| | | private String url = "https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php"; |
| | | /** 默认回调地址 */ |
| | | private String defaultNotifyUrl = "https://api.966120.com.cn/alipay/DspNotifyUrl.php"; |
| | | /** 超时时间(毫秒) */ |
| | | private int timeout = 30000; |
| | | } |
| | |
| | | |
| | | /** 签名类型 */ |
| | | private String signType = "MD5"; |
| | | |
| | | /** |
| | | * 第三方支付配置 |
| | | */ |
| | | private ThirdPartyConfig thirdParty = new ThirdPartyConfig(); |
| | | |
| | | } |
| | |
| | | /** |
| | | * 发起微信Native支付 |
| | | */ |
| | | @Anonymous() |
| | | @PostMapping("/wechat/native") |
| | | public AjaxResult createWechatNativePayment(@Validated @RequestBody PaymentRequest request) { |
| | | try { |
| | |
| | | /** |
| | | * 发起支付宝当面付 |
| | | */ |
| | | @Anonymous() |
| | | @PostMapping("/alipay/precreate") |
| | | public AjaxResult createAlipayPrecreate(@Validated @RequestBody PaymentRequest request) { |
| | | try { |
| | |
| | | /** |
| | | * 发起支付宝当面付(第三方接口) |
| | | */ |
| | | @Anonymous() |
| | | @PostMapping("/alipay/thirdparty/precreate") |
| | | public AjaxResult createAlipayThirdPartyPrecreate(@Validated @RequestBody PaymentRequest request) { |
| | | try { |
| | |
| | | /** |
| | | * 查询订单 |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/orders/{orderId}") |
| | | public AjaxResult getOrder(@PathVariable Long orderId) { |
| | | try { |
| | |
| | | /** |
| | | * 查询最新交易 |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/orders/{orderId}/transactions/latest") |
| | | public AjaxResult getLatestTransaction(@PathVariable Long orderId) { |
| | | try { |
| | |
| | | /** |
| | | * 查询支付宝第三方交易状态 |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/alipay/thirdparty/query/{orderId}") |
| | | public AjaxResult queryAlipayThirdPartyTradeStatus(@PathVariable Long orderId) { |
| | | try { |
| | |
| | | return AjaxResult.error("查询失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 轮询查询支付状态(用于回调失败的补偿机制) |
| | | * <p> |
| | | * 这个接口用于在支付回调未成功时,主动轮询查询支付状态。 |
| | | * 支持微信和支付宝(包括第三方支付宝)。 |
| | | * <p> |
| | | * 返回结果包含: |
| | | * - orderStatus: 订单状态 |
| | | * - transactionStatus: 交易状态 |
| | | * - tradeStatus: 第三方交易状态 |
| | | * - needPoll: 是否需要继续轮询 |
| | | * - message: 提示信息 |
| | | * <p> |
| | | * 建议轮询策略: |
| | | * 1. 首次轮询:立即轮询 |
| | | * 2. 后续轮询:间隔3-5秒 |
| | | * 3. 最多轮询20次,约100秒后停止 |
| | | * 4. 如果 needPoll=false,表示支付已完成或确认失败,无需继续轮询 |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/poll/{orderId}") |
| | | public AjaxResult pollPaymentStatus(@PathVariable Long orderId) { |
| | | try { |
| | | java.util.Map<String, Object> result = paymentService.pollPaymentStatus(orderId); |
| | | return AjaxResult.success(result); |
| | | } catch (Exception e) { |
| | | log.error("轮询查询支付状态失败", e); |
| | | return AjaxResult.error("查询失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据交易单号查询微信支付状态 |
| | | * <p> |
| | | * 该接口用于通过商户订单号(交易单号)直接查询微信支付状态。 |
| | | * <p> |
| | | * 返回结果包含: |
| | | * - success: 查询是否成功 |
| | | * - isPaid: 是否已支付 |
| | | * - tradeState: 交易状态(SUCCESS-支付成功,NOTPAY-未支付,CLOSED-已关闭等) |
| | | * - transactionId: 微信支付订单号 |
| | | * - totalFee: 订单金额(分) |
| | | * - timeEnd: 支付完成时间 |
| | | * - message: 提示信息 |
| | | * |
| | | * @param outTradeNo 商户订单号(交易单号) |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/wechat/query/{outTradeNo}") |
| | | public AjaxResult queryWechatTrade(@PathVariable String outTradeNo) { |
| | | try { |
| | | java.util.Map<String, Object> result = paymentService.queryWechatTradeByOutTradeNo(outTradeNo); |
| | | return AjaxResult.success(result); |
| | | } catch (Exception e) { |
| | | log.error("查询微信交易状态失败,交易单号: {}", outTradeNo, e); |
| | | return AjaxResult.error("查询失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据交易单号查询支付宝支付状态 |
| | | * <p> |
| | | * 该接口用于通过商户订单号(交易单号)直接查询支付宝支付状态。 |
| | | * <p> |
| | | * 返回结果包含: |
| | | * - success: 查询是否成功 |
| | | * - isPaid: 是否已支付 |
| | | * - tradeStatus: 交易状态(TRADE_SUCCESS-支付成功,WAIT_BUYER_PAY-等待付款,TRADE_CLOSED-已关闭等) |
| | | * - tradeNo: 支付宝交易号 |
| | | * - totalAmount: 订单金额(元) |
| | | * - sendPayDate: 交易支付时间 |
| | | * - buyerLogonId: 买家支付宝账号 |
| | | * - message: 提示信息 |
| | | * |
| | | * @param outTradeNo 商户订单号(交易单号) |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/alipay/query/{outTradeNo}") |
| | | public AjaxResult queryAlipayTrade(@PathVariable String outTradeNo) { |
| | | try { |
| | | java.util.Map<String, Object> result = paymentService.queryAlipayTradeByOutTradeNo(outTradeNo); |
| | | return AjaxResult.success(result); |
| | | } catch (Exception e) { |
| | | log.error("查询支付宝交易状态失败,交易单号: {}", outTradeNo, e); |
| | | return AjaxResult.error("查询失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | |
| | | * 微信支付回调 |
| | | */ |
| | | @Anonymous |
| | | @PostMapping("/wechat") |
| | | @PostMapping("/wechat/notify") |
| | | public String wechatNotify(HttpServletRequest request) { |
| | | try { |
| | | log.info("接收到微信支付回调"); |
| | |
| | | * 支付宝回调 |
| | | */ |
| | | @Anonymous |
| | | @PostMapping("/alipay") |
| | | @PostMapping("/alipay/notify") |
| | | public String alipayNotify(HttpServletRequest request) { |
| | | try { |
| | | log.info("接收到支付宝回调"); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 第三方支付宝回调(GET请求) |
| | | * <p> |
| | | * 第三方支付宝通过GET方式回调,参数通过URL传递 |
| | | * <p> |
| | | * 参数格式: |
| | | * method=xxx&APPID=xxx&PaidMoneyType=xxx&PaidMoney=xxx&PaidRemarks=xxx&UnixTime=xxx&Sign=xxx |
| | | * <p> |
| | | * 参数说明: |
| | | * - method: 方法名 |
| | | * - APPID: 应用ID |
| | | * - PaidMoneyType: 支付类型 |
| | | * - PaidMoney: 支付金额(单位:分) |
| | | * - PaidRemarks: 支付备注(包含订单号和交易号信息) |
| | | * - UnixTime: 时间戳 |
| | | * - Sign: 签名 |
| | | * <p> |
| | | * 示例: |
| | | * GET /api/pay/notify/alipay/thirdparty?method=alipay.pay&APPID=123456&PaidMoneyType=alipay&PaidMoney=100&PaidRemarks=1234567890&UnixTime=1638360000&Sign=abc123 |
| | | */ |
| | | @Anonymous |
| | | @GetMapping("/alipay/thirdparty") |
| | | public String alipayThirdPartyNotify(HttpServletRequest request) { |
| | | try { |
| | | log.info("接收到第三方支付宝回调(GET请求)"); |
| | | |
| | | // 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. 第三方支付宝不需要验签,或者根据第三方平台的签名规则进行验证 |
| | | // 这里直接处理,如果需要验签可以在Service层添加 |
| | | |
| | | // 3. 处理回调(更新订单状态、触发业务回调) |
| | | paymentNotifyService.processAlipayThirdPartyNotify(params); |
| | | |
| | | // 4. 返回第三方支付宝要求的应答 |
| | | return "success"; |
| | | } catch (Exception e) { |
| | | log.error("处理第三方支付宝回调失败", e); |
| | | return "fail"; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 第三方微信回调(GET请求) |
| | | * <p> |
| | | * 第三方微信通过GET方式回调,参数通过URL传递 |
| | | * <p> |
| | | * 参数格式: |
| | | * method=xxx&APPID=xxx&PaidMoneyType=xxx&PaidMoney=xxx&PaidRemarks=xxx&UnixTime=xxx |
| | | * <p> |
| | | * 参数说明: |
| | | * - method: 方法名 |
| | | * - APPID: 应用ID |
| | | * - PaidMoneyType: 支付类型 |
| | | * - PaidMoney: 支付金额(单位:分) |
| | | * - PaidRemarks: 支付备注(包含订单号和交易号信息) |
| | | * - UnixTime: 时间戳 |
| | | * <p> |
| | | * 示例: |
| | | * GET /api/pay/notify/wechat/thirdparty?method=wechat.pay&APPID=123456&PaidMoneyType=wechat&PaidMoney=100&PaidRemarks=1234567890&UnixTime=1638360000 |
| | | */ |
| | | @Anonymous |
| | | @GetMapping("/wechat/thirdparty") |
| | | public String wechatThirdPartyNotify(HttpServletRequest request) { |
| | | try { |
| | | log.info("接收到第三方微信回调(GET请求)"); |
| | | |
| | | // 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. 第三方微信不需要验签,或者根据第三方平台的签名规则进行验证 |
| | | // 这里直接处理,如枟需要验签可以在Service层添加 |
| | | |
| | | // 3. 处理回调(更新订单状态、触发业务回调) |
| | | paymentNotifyService.processWechatThirdPartyNotify(params); |
| | | |
| | | // 4. 返回第三方微信要求的应答 |
| | | return "success"; |
| | | } catch (Exception e) { |
| | | log.error("处理第三方微信回调失败", e); |
| | | return "fail"; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 读取请求体 |
| | | */ |
| | | private String readRequestBody(HttpServletRequest request) throws Exception { |
| | |
| | | mchId: 1573728151 |
| | | appName: 民航医疗快线 |
| | | mchKey: Xz0ClPK3f5sCeT6SGTaha1vpVmyUFcbp |
| | | notifyUrl: https://dsp.966120.com/api/pay/notify/wechat |
| | | notifyUrl: https://dsp.966120.com.cn/api/pay/notify/wechat/notify |
| | | signType: MD5 |
| | | checkSign: false |
| | | thirdParty: |
| | | enabled: false |
| | | url: https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php |
| | | defaultNotifyUrl: https://api.966120.com.cn/weixin/DspNotifyUrl.asp |
| | | timeout: 30000 |
| | | |
| | | # 支付宝配置 |
| | | alipay: |
| | |
| | | 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 |
| | | notifyUrl: https://dsp.966120.com.cn/api/pay/notify/alipay/notify |
| | | signType: RSA2 |
| | | checkSign: false |
| | | # 支付方式: OFFICIAL(官方支付宝) 或 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 |
| | | # defaultNotifyUrl: https://api.966120.com.cn/alipay/DspNotifyUrl.php |
| | | defaultNotifyUrl: https://dsp.966120.com.cn/api/pay/notify/alipay/notify |
| | | timeout: 30000 |
| | | |
| | | # 业务回调配置 |
| | |
| | | |
| | | // 生成回调地址 |
| | | String callbackUrl = callbackBaseUrl + "/payment/callback/" + provider.toLowerCase(); |
| | | |
| | | payment.setCallbackUrl(callbackUrl); |
| | | |
| | | try { |