package cc.mrbird.febs.pay.util; import cc.mrbird.febs.pay.model.RefundStatus; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.springframework.stereotype.Service; import java.util.List; import java.util.concurrent.*; @Service(value="FiuuRefundUtil") public class FiuuRefundUtil { private static final String API_BASE_URL = "https://api.fiuu.com/RMS/API/refundAPI/"; private static final String MERCHANT_ID = "e2umart01"; private static final String VERIFY_KEY = "4e3a4ed58e62ddbfacf41f6d5ec56bf2"; private static final int MAX_RETRIES = 3; private static final int POLL_INTERVAL = 1000; // 5秒 private static final int TIMEOUT = 60000; // 60秒超时 private final ObjectMapper objectMapper = new ObjectMapper(); // 退款状态查询(根据TxnID) public RefundStatus queryByTxnId(String txnId) throws Exception { String signature = HashUtils.md5(txnId + MERCHANT_ID + VERIFY_KEY); String url = API_BASE_URL + "q_by_txn.php?TxnID=" + txnId + "&MerchantID=" + MERCHANT_ID + "&Signature=" + signature; return executeQuery(url); } // 退款状态查询(根据RefID) public RefundStatus queryByRefId(String refId) throws Exception { String signature = HashUtils.md5(refId + MERCHANT_ID + VERIFY_KEY); String url = API_BASE_URL + "q_by_refID.php?RefID=" + refId + "&MerchantID=" + MERCHANT_ID + "&Signature=" + signature; return executeQuery(url); } private RefundStatus executeQuery(String url) throws Exception { HttpGet request = new HttpGet(url); try (CloseableHttpClient client = HttpClients.createDefault()) { String response = EntityUtils.toString(client.execute(request).getEntity()); List refundStatusList = objectMapper.readValue( response, objectMapper.getTypeFactory().constructCollectionType(List.class, RefundStatus.class)); return refundStatusList.get(0); } } // 异步轮询退款状态 public RefundStatus pollRefundStatus(String txnId) throws Exception { ExecutorService executor = Executors.newSingleThreadExecutor(); Future future = executor.submit(() -> { int retryCount = 0; while (retryCount < MAX_RETRIES) { try { RefundStatus status = queryByTxnId(txnId); if (!"pending".equals(status.getStatus())) { return status; } Thread.sleep(POLL_INTERVAL); retryCount++; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Polling interrupted", e); } } throw new TimeoutException("Max retries reached"); }); try { return future.get(TIMEOUT, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { future.cancel(true); throw new RuntimeException("Refund status check timeout"); } finally { executor.shutdown(); } } }