| | |
| | | import com.talentcard.server.business.MoneyUtils; |
| | | import com.talentcard.server.business.order.OrderRefundRequest; |
| | | import com.talentcard.server.business.payment.huifu.HuifuPaymentGatewayException; |
| | | import com.talentcard.server.business.payment.huifu.HuifuPaymentProperties; |
| | | import com.talentcard.server.business.payment.huifu.HuifuRefundCommand; |
| | | import com.talentcard.server.business.payment.huifu.HuifuRefundGateway; |
| | | import com.talentcard.server.common.BusinessException; |
| | |
| | | private final JdbcTemplate jdbc; |
| | | private final TransactionTemplate transactions; |
| | | private final HuifuRefundGateway gateway; |
| | | private final HuifuPaymentProperties huifuPaymentProperties; |
| | | |
| | | public HuifuRefundService(JdbcTemplate jdbc, TransactionTemplate transactions, HuifuRefundGateway gateway) { |
| | | public HuifuRefundService(JdbcTemplate jdbc, TransactionTemplate transactions, HuifuRefundGateway gateway, HuifuPaymentProperties huifuPaymentProperties) { |
| | | this.jdbc = jdbc; |
| | | this.transactions = transactions; |
| | | this.gateway = gateway; |
| | | this.huifuPaymentProperties = huifuPaymentProperties; |
| | | } |
| | | |
| | | public Map<String, Object> request(String orderNo, OrderRefundRequest request) { |
| | |
| | | Map<String, Object> response; |
| | | try { |
| | | response = gateway.createRefund(new HuifuRefundCommand( |
| | | prepared.refundNo(), prepared.paymentNo(), prepared.originalProviderNo(), |
| | | prepared.refundNo(), prepared.huifuId(), prepared.paymentNo(), prepared.originalProviderNo(), |
| | | prepared.originalRequestDate(), prepared.refundAmount())); |
| | | } catch (HuifuPaymentGatewayException exception) { |
| | | if (!exception.isOutcomeUnknown()) { |
| | |
| | | if (number(refund.get("refundStatus")) != 1) return refund; |
| | | HuifuRefundCommand command = new HuifuRefundCommand( |
| | | refundNo, |
| | | refundHuifuId(refund.get("providerHuifuId")), |
| | | text(refund.get("providerOriginalReqSeqId")), |
| | | text(refund.get("providerOriginalNo")), |
| | | text(refund.get("providerOriginalReqDate")), |
| | |
| | | public Map<String, Object> detail(String refundNo) { |
| | | List<Map<String, Object>> rows = rows(""" |
| | | select r.id, r.order_id, r.refund_no, r.order_no, r.order_type, r.paid_amount, r.refund_amount, |
| | | r.refundable_amount, r.refund_status, r.provider, r.provider_refund_no, |
| | | r.refundable_amount, r.refund_status, r.provider, r.provider_refund_no, r.provider_huifu_id, |
| | | r.provider_original_no, r.provider_original_req_seq_id, r.provider_original_req_date, |
| | | r.provider_response_code, r.provider_response_message, r.reason, r.operator_name, r.refund_time |
| | | from order_refund r |
| | |
| | | List<Map<String, Object>> orders = rows(""" |
| | | select o.id, o.order_no, o.talent_id, o.card_id, o.benefit_id, o.merchant_id, o.order_type, |
| | | o.pay_amount, o.order_status, o.verify_status, |
| | | p.payment_no, p.provider_transaction_no, p.created_at as payment_created_at |
| | | p.payment_no, p.provider_transaction_no, p.created_at as payment_created_at, |
| | | coalesce(p.provider_huifu_id, o.collect_huifu_id) as huifu_id |
| | | from benefit_order o |
| | | left join payment_transaction p on p.order_id = o.id and p.provider = ? and p.status = 'SUCCEEDED' |
| | | where o.deleted = 0 and o.order_no = ? |
| | |
| | | String paymentNo = text(order.get("paymentNo")); |
| | | String providerNo = text(order.get("providerTransactionNo")); |
| | | if (paymentNo.isBlank() && providerNo.isBlank()) throw new BusinessException(ErrorCode.CONFLICT, "Payment transaction is not ready for refund"); |
| | | String huifuId = refundHuifuId(order.get("huifuId")); |
| | | String refundNo = BusinessIdGenerator.next("R"); |
| | | String requestDate = requestDate(order.get("paymentCreatedAt")); |
| | | jdbc.update(""" |
| | | insert into order_refund(refund_no, order_id, order_no, talent_id, card_id, benefit_id, merchant_id, |
| | | order_type, paid_amount, refund_amount, refundable_amount, refund_status, |
| | | provider, provider_original_no, provider_original_req_seq_id, provider_original_req_date, |
| | | provider, provider_huifu_id, provider_original_no, provider_original_req_seq_id, provider_original_req_date, |
| | | reason, operator_name) |
| | | values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?) |
| | | values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?) |
| | | """, |
| | | refundNo, order.get("id"), order.get("orderNo"), order.get("talentId"), order.get("cardId"), |
| | | order.get("benefitId"), order.get("merchantId"), order.get("orderType"), paidAmount, refundAmount, |
| | | refundableAmount - refundAmount, PROVIDER, providerNo, paymentNo, requestDate, request.reason(), request.operatorName()); |
| | | return new PreparedRefund(refundNo, number(order.get("id")), paymentNo, providerNo, requestDate, refundAmount); |
| | | refundableAmount - refundAmount, PROVIDER, huifuId, providerNo, paymentNo, requestDate, request.reason(), request.operatorName()); |
| | | return new PreparedRefund(refundNo, number(order.get("id")), huifuId, paymentNo, providerNo, requestDate, refundAmount); |
| | | } |
| | | |
| | | private void markSucceeded(PreparedRefund prepared, ProviderResult result) { |
| | |
| | | return total == null ? 0 : total; |
| | | } |
| | | |
| | | /** |
| | | * The merchant a refund must be requested from: the sub-merchant fixed on the payment or the |
| | | * order. Payments made before the collecting sub-merchant was recorded were collected by the |
| | | * configured platform merchant, so those fall back to it instead of the refund failing. |
| | | */ |
| | | private String refundHuifuId(Object storedHuifuId) { |
| | | String resolved = text(storedHuifuId); |
| | | return resolved.isBlank() ? text(huifuPaymentProperties.getMerchantId()) : resolved; |
| | | } |
| | | |
| | | private String requestDate(Object value) { |
| | | if (value instanceof LocalDateTime dateTime) return REQUEST_DATE.format(dateTime); |
| | | if (value instanceof LocalDate date) return date.format(DateTimeFormatter.BASIC_ISO_DATE); |
| | |
| | | private long number(Object value) { return value instanceof Number number ? number.longValue() : 0; } |
| | | private String text(Object value) { return value == null ? "" : String.valueOf(value).trim(); } |
| | | |
| | | private record PreparedRefund(String refundNo, long orderId, String paymentNo, String originalProviderNo, String originalRequestDate, long refundAmount) { |
| | | private PreparedRefund(String refundNo, long orderId, long refundAmount) { this(refundNo, orderId, "", "", "", refundAmount); } |
| | | private record PreparedRefund(String refundNo, long orderId, String huifuId, String paymentNo, String originalProviderNo, String originalRequestDate, long refundAmount) { |
| | | private PreparedRefund(String refundNo, long orderId, long refundAmount) { this(refundNo, orderId, "", "", "", "", refundAmount); } |
| | | } |
| | | |
| | | private record ProviderResult(boolean success, boolean failed, String code, String message, String providerNo) { |