fix(payment): 人才端列表把「已退款」和「未支付取消」分开显示
退款订单在人才端列表里显示成「已取消」——这是上一轮刻意的取舍:列表状态只有
pending_payment / pending_verification / completed / cancelled 这套桶值,退款被我并进了
cancelled 桶(避免给列表的页签与筛选凭空加词),精确状态只在订单状态页(退款处理 / 退款成功)。
于是列表徽标一律渲染成「已取消」,退款中和已退款都看不出来,容易让人以为钱没回来。
改法:桶值不动(页签、筛选、点击跳转都不变),把退款维度带进列表记录,文案在这一桶内细分。
- 后端 UsageRecordPortalService:订单分支加选 o.refund_status(联合查询的另外三个分支补
null as refund_status,保持列数一致),列表记录因此带出 refundStatus。
- 人才端订单列表:statusCode 仍是 cancelled,徽标文案按 refundStatus 细分——1 退款中、
2 已退款、否则仍是已取消;页签「已取消」改为「已取消/已退款」,与商户端口径一致。
测试:PendingRefundConfirmationTests 增至 9 项,新增「列表要能区分未支付取消与钱已退回」,
同时钉住退款中(refundStatus=1 且状态仍是 cancelled)与已退款(refundStatus=2)。
全量 274 项,仅 docs/agents/testing.md 所列 3 项基线失败,无新增。
| | |
| | | { label: '待支付', value: 'pending_payment' }, |
| | | { label: '待核销', value: 'pending_verification' }, |
| | | { label: '已完成', value: 'completed' }, |
| | | { label: '已取消', value: 'cancelled' }, |
| | | { label: '已取消/已退款', value: 'cancelled' }, |
| | | { label: '待处理', value: 'processing' }, |
| | | { label: '已领取', value: 'received' } |
| | | ] |
| | |
| | | const amount = Number(value) |
| | | return isNaN(amount) ? '' : '¥' + amount.toFixed(2) |
| | | } |
| | | /** |
| | | * 已完成/待核销/待支付之外的订单都归在「已取消」这一桶里,但离开支付流程的原因不同:未支付被取消, |
| | | * 还是钱已经退回来了。退款维度只改这一桶的文案,状态码与点击跳转不变。 |
| | | */ |
| | | const statusLabel = (status: string, source: any): string => { |
| | | if (status == 'cancelled') { |
| | | const refundStatus = source != null && source.refundStatus != null ? Number(source.refundStatus) : 0 |
| | | if (refundStatus == 1) return '退款中' |
| | | if (refundStatus == 2) return '已退款' |
| | | } |
| | | return statusText(status) |
| | | } |
| | | const mapRecord = (source: any): any => { |
| | | const merchantType = source != null && source.merchantType != null ? source.merchantType as string : 'service' |
| | | const status = source != null && source.status != null ? source.status as string : '' |
| | |
| | | merchantType: merchantType, |
| | | time: formatTime(source != null ? source.occurredAt : null), |
| | | statusCode: status, |
| | | status: statusText(status), |
| | | status: statusLabel(status, source), |
| | | amount: formatAmount(source != null ? source.amount : null), |
| | | icon: getMerchantIcon(merchantType), |
| | | iconTone: getMerchantIconTone(merchantType), |
| | |
| | | when o.verify_status = 1 then 'completed' |
| | | else 'pending_verification' |
| | | end as status, |
| | | o.refund_status as refund_status, |
| | | o.pay_amount as amount_cent, |
| | | 'ORDER_DETAIL' as target_detail_type |
| | | from benefit_order o |
| | |
| | | else 'processing' |
| | | end as status, |
| | | null as amount_cent, |
| | | 'SERVICE_APPLICATION_DETAIL' as target_detail_type |
| | | 'SERVICE_APPLICATION_DETAIL' as target_detail_type, |
| | | null as refund_status |
| | | from service_application s |
| | | where s.deleted = 0 |
| | | and exists ( |
| | |
| | | r.received_at as occurred_at, |
| | | 'received' as status, |
| | | null as amount_cent, |
| | | 'MOVIE_TICKET_DETAIL' as target_detail_type |
| | | 'MOVIE_TICKET_DETAIL' as target_detail_type, |
| | | null as refund_status |
| | | from movie_ticket_record r |
| | | left join cinema_coupon_claim cc on cc.claim_no = r.receive_no and cc.deleted = 0 |
| | | where r.deleted = 0 |
| | |
| | | cl.created_at as occurred_at, |
| | | 'received' as status, |
| | | null as amount_cent, |
| | | 'TICKET_PACKAGE_DETAIL' as target_detail_type |
| | | 'TICKET_PACKAGE_DETAIL' as target_detail_type, |
| | | null as refund_status |
| | | from ticket_package_claim cl |
| | | join ticket_package p on p.id = cl.package_id and p.deleted = 0 |
| | | left join merchant m on m.id = cl.merchant_id and m.deleted = 0 |
| | |
| | | .containsEntry("statusText", "已退款"); |
| | | } |
| | | |
| | | @Test |
| | | void theTalentOrderListCarriesTheRefundStateSoARefundIsNotJustAnotherCancellation() { |
| | | RefundingOrder refunded = refundingOrder(2); |
| | | |
| | | assertThat(((Number) recordOf(refunded.orderNo()).get("refundStatus")).intValue()) |
| | | .as("列表要能区分「未支付被取消」和「钱已经退回来」") |
| | | .isEqualTo(2); |
| | | |
| | | RefundingOrder refunding = refundingOrder(1); |
| | | when(refundGateway.queryRefund(any(), any())).thenReturn(providerResponse(Map.of( |
| | | "trans_stat", "P", "resp_code", "00000100", "sub_resp_desc", "交易处理中", "hf_seq_id", "HF-PENDING-" + suffix))); |
| | | |
| | | Map<String, Object> refundingRecord = recordOf(refunding.orderNo()); |
| | | assertThat(((Number) refundingRecord.get("refundStatus")).intValue()).isEqualTo(1); |
| | | assertThat(refundingRecord).containsEntry("status", "cancelled"); |
| | | } |
| | | |
| | | /** A paid order whose refund was submitted and has no final provider answer yet. */ |
| | | private RefundingOrder refundingOrder(int refundStatus) { |
| | | String orderNo = "REFUND-" + suffix + "-" + (++sequence); |