package com.ruoyi.app.pay; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONArray; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.ruoyi.app.pay.dto.LinePayCreateResult; import com.ruoyi.app.pay.dto.LinePayQueryResult; import com.ruoyi.app.utils.linepay.LinePayClient; import com.ruoyi.app.utils.linepay.LinePayCredential; import com.ruoyi.app.utils.linepay.LinePayRequest; import com.ruoyi.app.utils.linepay.LinePayResponse; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.system.domain.PosOrder; import com.ruoyi.system.domain.PosOrderLinePayment; import com.ruoyi.system.domain.PosOrderLineRefund; import com.ruoyi.system.domain.PosStoreLinePay; import com.ruoyi.system.service.IPosOrderLinePaymentService; import com.ruoyi.system.service.IPosOrderLineRefundService; import com.ruoyi.system.service.IPosOrderService; import com.ruoyi.system.service.IPosStoreLinePayService; import org.springframework.dao.DuplicateKeyException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.beans.factory.annotation.Value; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.UUID; /** LINE Pay create, query, confirm, retrieval and payment-fact orchestration. */ @Service public class LinePayService { public static final String PAY_TYPE_LINE = "3"; private static final List DEFINITE_REQUEST_FAILURE_CODES = List.of( "1104", "1105", "1106", "1124", "1178", "1183", "1194", "2101", "2102"); private static final Set CREDENTIAL_AUTH_FAILURE_CODES = Set.of("1104", "1105", "1106"); @Value("${line-pay.reconcile.auth-deadline-minutes:30}") private long authDeadlineMinutes = 30L; @Value("${line-pay.reconcile.unknown-deadline-hours:24}") private long unknownDeadlineHours = 24L; @Value("${line-pay.reconcile.row-lease-seconds:120}") private long rowLeaseSeconds = 120L; private final IPosOrderService orderService; private final IPosStoreLinePayService credentialService; private final IPosOrderLinePaymentService paymentService; private final IPosOrderLineRefundService refundService; private final LinePayClient linePayClient; private final LinePayFactService factService; private final LinePayGatewayAuditService auditService; private final PaymentCreateGuardService createGuard; public LinePayService(IPosOrderService orderService, IPosStoreLinePayService credentialService, IPosOrderLinePaymentService paymentService, IPosOrderLineRefundService refundService, LinePayClient linePayClient, LinePayFactService factService) { this(orderService, credentialService, paymentService, refundService, linePayClient, factService, null, null); } @Autowired public LinePayService(IPosOrderService orderService, IPosStoreLinePayService credentialService, IPosOrderLinePaymentService paymentService, IPosOrderLineRefundService refundService, LinePayClient linePayClient, LinePayFactService factService, LinePayGatewayAuditService auditService, PaymentCreateGuardService createGuard) { this.orderService = orderService; this.credentialService = credentialService; this.paymentService = paymentService; this.refundService = refundService; this.linePayClient = linePayClient; this.factService = factService; this.auditService = auditService; this.createGuard = createGuard; } public LinePayCreateResult create(Long userId, String ddId) { if (createGuard != null) { return createGuard.withLock(ddId, () -> createUnderLock(userId, ddId)); } return createUnderLock(userId, ddId); } private LinePayCreateResult createUnderLock(Long userId, String ddId) { PosOrder order = requirePayableOrder(userId, ddId); requireSingleStoreOrder(order); PosStoreLinePay credential = credentialService.getEnabledCurrent(order.getMdId()); if (credential == null) { throw new ServiceException("LINE Pay is not enabled for this store"); } PosOrderLinePayment active = paymentService.getActiveByDdId(ddId); if (active != null) { return createResult(active, true); } IntentSelection selection = createIntent(order, credential); PosOrderLinePayment intent = selection.payment(); if (!selection.created()) { return createResult(intent, true); } try { LinePayResponse response = gatewayCall("REQUEST", "APP", intent, () -> linePayClient.request(toCredential(credential), new LinePayRequest(intent.getLineOrderId(), ddId, "Order " + ddId, order.getAmount(), "TWD"))); if (!response.isSuccess() || response.transactionId() == null) { if (isDefiniteRequestFailure(response)) { if (paymentService.markTerminal(intent.getId(), intent.getVersion(), "REQUESTING", "FAILED") == 1) { intent.setStatus("FAILED"); intent.setActiveDdId(null); return createResult(intent, false); } PosOrderLinePayment latest = paymentService.getById(intent.getId()); if (latest != null) { return createResult(latest, true); } throw new ServiceException("LINE Pay payment state requires reconciliation"); } paymentService.markRequestUnknown(intent.getId(), intent.getVersion(), shortly(), unknownDeadline()); intent.setStatus("REQUEST_UNKNOWN"); return createResult(intent, false); } JSONObject paymentUrl = response.info() == null ? null : response.info().getJSONObject("paymentUrl"); String web = paymentUrl == null ? null : paymentUrl.getString("web"); String app = paymentUrl == null ? null : paymentUrl.getString("app"); if (web == null || paymentService.markRequestSucceeded(intent.getId(), intent.getVersion(), response.transactionId(), web, app, shortly(), authDeadline()) != 1) { paymentService.markRequestUnknown(intent.getId(), intent.getVersion(), shortly(), unknownDeadline()); intent.setStatus("REQUEST_UNKNOWN"); return createResult(intent, false); } intent.setTransactionId(response.transactionId()); intent.setPaymentUrlWeb(web); intent.setPaymentUrlApp(app); intent.setStatus("WAITING_AUTH"); return createResult(intent, false); } catch (Exception unknown) { paymentService.markRequestUnknown(intent.getId(), intent.getVersion(), shortly(), unknownDeadline()); intent.setStatus("REQUEST_UNKNOWN"); return createResult(intent, false); } } public LinePayQueryResult query(Long userId, String ddId) { PosOrder order = requireOwnedOrder(userId, ddId); List attempts = paymentService.getByDdId(ddId); PosOrderLinePayment selected = selectForApp(order, attempts); LinePayQueryResult result = new LinePayQueryResult(); result.setDdId(ddId); result.setOrderPayStatus(order.getPayStatus()); if (selected != null) { result.setPaymentId(selected.getId()); result.setPaymentStatus(selected.getStatus()); result.setTransactionId(selected.getTransactionId()); result.setUpdatedAt(selected.getUpdateTime()); PosOrderLineRefund refund = refundService.getByPaymentId(selected.getId()); result.setRefundStatus(refund == null ? null : refund.getStatus()); } return result; } public PosOrderLinePayment findByLineOrderId(String lineOrderId, String transactionId) { PosOrderLinePayment payment = paymentService.getByLineOrderId(lineOrderId); if (payment == null) { return null; } if (transactionId != null && payment.getTransactionId() != null && !transactionId.equals(payment.getTransactionId())) { return null; } return payment; } public String reconcilePayment(Long paymentId, String source) { return reconcilePayment(paymentId, source, null); } public String reconcilePayment(Long paymentId, String source, String reconcileLeaseOwner) { PosOrderLinePayment payment = paymentService.getById(paymentId); if (payment == null) { throw new ServiceException("LINE Pay payment not found"); } if (payment.getReconcileDeadline() != null && !new Date().before(payment.getReconcileDeadline())) { return finalDeadlineReconcile(payment, source); } PosStoreLinePay credential = credentialService.getById(payment.getCredentialId()); if (credential == null) { throw new ServiceException("LINE Pay credential version not found"); } try { RetrieveResult retrieveResult = retrieveWithCredentialRecovery( payment, source, credential, toCredential(credential)); LinePayResponse retrieve = retrieveResult.response(); LinePayCredential gatewayCredential = retrieveResult.credential(); JSONObject captured = capturedPayment(retrieve, payment); if (captured != null) { String fullRefundTransactionId = fullRefundTransactionId(captured, payment.getAmount()); if (fullRefundTransactionId != null) { factService.applyPaidAndRefundedFact(payment.getId(), captured.getString("transactionId"), paymentProvider(retrieve), new Date(), fullRefundTransactionId, new Date()); return "REFUNDED"; } if (hasRefundEvidence(captured)) { if (isLocallyRefunded(payment.getId())) { return "REFUNDED"; } factService.applyPaidWithRefundReviewFact(payment.getId(), captured.getString("transactionId"), paymentProvider(retrieve), new Date()); return "MANUAL_REVIEW"; } factService.applyPaidFact(payment.getId(), captured.getString("transactionId"), paymentProvider(retrieve), new Date()); return "PAID"; } if (!("1150".equals(retrieve.returnCode()) || retrieve.isSuccess())) { return payment.getStatus(); } if (!"1150".equals(retrieve.returnCode()) || payment.getTransactionId() == null || !canCheckOrConfirm(payment.getStatus())) { return payment.getStatus(); } LinePayResponse check = gatewayCall("CHECK", source, payment, () -> linePayClient.check(gatewayCredential, payment.getTransactionId())); return handleCheck(payment, gatewayCredential, check, source, reconcileLeaseOwner); } catch (Exception unknown) { if ("CONFIRMING".equals(payment.getStatus())) { paymentService.markConfirmUnknown(payment.getId(), payment.getVersion(), shortly(), unknownDeadline()); return "CONFIRM_UNKNOWN"; } return payment.getStatus(); } } private String finalDeadlineReconcile(PosOrderLinePayment payment, String source) { PosStoreLinePay credential = credentialService.getById(payment.getCredentialId()); if (credential == null) { paymentService.markManualReview(payment.getId(), payment.getVersion(), payment.getStatus()); return "MANUAL_REVIEW"; } try { RetrieveResult retrieveResult = retrieveWithCredentialRecovery( payment, source, credential, toCredential(credential)); LinePayCredential gatewayCredential = retrieveResult.credential(); LinePayResponse retrieve = retrieveResult.response(); JSONObject captured = capturedPayment(retrieve, payment); if (captured != null) { String fullRefundTransactionId = fullRefundTransactionId(captured, payment.getAmount()); if (fullRefundTransactionId != null) { factService.applyPaidAndRefundedFact(payment.getId(), captured.getString("transactionId"), paymentProvider(retrieve), new Date(), fullRefundTransactionId, new Date()); return "REFUNDED"; } if (hasRefundEvidence(captured)) { if (isLocallyRefunded(payment.getId())) { return "REFUNDED"; } factService.applyPaidWithRefundReviewFact(payment.getId(), captured.getString("transactionId"), paymentProvider(retrieve), new Date()); return "MANUAL_REVIEW"; } factService.applyPaidFact(payment.getId(), captured.getString("transactionId"), paymentProvider(retrieve), new Date()); return "PAID"; } if ("1150".equals(retrieve.returnCode()) && payment.getTransactionId() != null && canCheckOrConfirm(payment.getStatus())) { LinePayResponse check = gatewayCall("CHECK", source, payment, () -> linePayClient.check(gatewayCredential, payment.getTransactionId())); if ("0121".equals(check.returnCode()) || "0122".equals(check.returnCode())) { String terminal = "0121".equals(check.returnCode()) ? "CANCELLED_OR_EXPIRED" : "FAILED"; paymentService.markTerminal(payment.getId(), payment.getVersion(), payment.getStatus(), terminal); return terminal; } } } catch (Exception ignored) { // The final read was inconclusive; stop automatic scanning for manual review. } paymentService.markManualReview(payment.getId(), payment.getVersion(), payment.getStatus()); return "MANUAL_REVIEW"; } private String handleCheck(PosOrderLinePayment payment, LinePayCredential credential, LinePayResponse check, String source, String reconcileLeaseOwner) throws Exception { String code = check.returnCode(); if ("0000".equals(code)) { return payment.getStatus(); } if ("0121".equals(code) || "0122".equals(code)) { String terminal = "0121".equals(code) ? "CANCELLED_OR_EXPIRED" : "FAILED"; paymentService.markTerminal(payment.getId(), payment.getVersion(), payment.getStatus(), terminal); return terminal; } if ("0123".equals(code)) { LinePayResponse retrieve = gatewayCall("RETRIEVE", source, payment, () -> payment.getTransactionId() == null ? linePayClient.retrieveByOrderId(credential, payment.getLineOrderId()) : linePayClient.retrieveByTransactionId(credential, payment.getTransactionId())); JSONObject captured = capturedPayment(retrieve, payment); if (captured != null) { String fullRefundTransactionId = fullRefundTransactionId(captured, payment.getAmount()); if (fullRefundTransactionId != null) { factService.applyPaidAndRefundedFact(payment.getId(), captured.getString("transactionId"), paymentProvider(retrieve), new Date(), fullRefundTransactionId, new Date()); return "REFUNDED"; } if (hasRefundEvidence(captured)) { if (isLocallyRefunded(payment.getId())) { return "REFUNDED"; } factService.applyPaidWithRefundReviewFact(payment.getId(), captured.getString("transactionId"), paymentProvider(retrieve), new Date()); return "MANUAL_REVIEW"; } factService.applyPaidFact(payment.getId(), captured.getString("transactionId"), paymentProvider(retrieve), new Date()); return "PAID"; } return payment.getStatus(); } if (!"0110".equals(code)) { return payment.getStatus(); } if (!canCheckOrConfirm(payment.getStatus())) { return payment.getStatus(); } PosOrder order = orderService.getOne(new QueryWrapper().eq("dd_id", payment.getDdId())); if (order == null || !isPayableOrderState(order)) { paymentService.markAuthDoneOrderCancelled(payment.getId(), payment.getVersion(), payment.getStatus(), shortly(), authDeadline()); return "AUTH_DONE_ORDER_CANCELLED"; } if (!LinePayService.PAY_TYPE_LINE.equals(order.getPayType()) || Long.valueOf(1L).equals(order.getPayStatus()) || !order.getMdId().equals(payment.getStoreId()) || !order.getAmount().equals(payment.getAmount())) { return payment.getStatus(); } String leaseOwner = reconcileLeaseOwner == null ? (source == null ? "LINE" : source) + "-" + UUID.randomUUID() : reconcileLeaseOwner; if (paymentService.claimConfirm(payment.getId(), payment.getVersion(), payment.getStatus(), leaseOwner, leaseUntil()) != 1) { return payment.getStatus(); } LinePayResponse confirm; try { confirm = gatewayCall("CONFIRM", source, payment, () -> linePayClient.confirm(credential, payment.getTransactionId(), payment.getAmount(), payment.getCurrency())); } catch (Exception unknown) { paymentService.markConfirmUnknown(payment.getId(), payment.getVersion() + 1, shortly(), unknownDeadline()); return "CONFIRM_UNKNOWN"; } if (confirm.isSuccess()) { if (!confirmedPayment(confirm, payment)) { paymentService.markConfirmUnknown(payment.getId(), payment.getVersion() + 1, shortly(), unknownDeadline()); return "CONFIRM_UNKNOWN"; } factService.applyPaidFact(payment.getId(), payment.getTransactionId(), paymentProvider(confirm), new Date()); return "PAID"; } paymentService.markConfirmUnknown(payment.getId(), payment.getVersion() + 1, shortly(), unknownDeadline()); return "CONFIRM_UNKNOWN"; } private static JSONObject capturedPayment(LinePayResponse response, PosOrderLinePayment payment) { if (response == null || !response.isSuccess()) { return null; } JSONObject root = JSONObject.parseObject(response.rawBody()); JSONArray info = root.getJSONArray("info"); if (info == null || info.size() != 1) { return null; } JSONObject transaction = info.getJSONObject(0); String gatewayTransactionId = transaction.getString("transactionId"); boolean transactionMatches = payment.getTransactionId() == null ? gatewayTransactionId != null : payment.getTransactionId().equals(gatewayTransactionId); Integer gatewayAmount = paymentAmount(transaction); return transactionMatches && payment.getLineOrderId().equals(transaction.getString("orderId")) && payment.getCurrency().equals(transaction.getString("currency")) && "PAYMENT".equals(transaction.getString("transactionType")) && gatewayAmount != null && gatewayAmount.equals(payment.getAmount()) ? transaction : null; } private static Integer paymentAmount(JSONObject transaction) { JSONArray payInfo = transaction.getJSONArray("payInfo"); if (payInfo == null || payInfo.isEmpty()) { return null; } long total = 0L; for (int i = 0; i < payInfo.size(); i++) { JSONObject item = payInfo.getJSONObject(i); Integer amount = item == null ? null : item.getInteger("amount"); if (amount == null || amount <= 0) { return null; } total += amount; } return total <= Integer.MAX_VALUE ? (int) total : null; } private static String fullRefundTransactionId(JSONObject transaction, int expectedAmount) { JSONArray refunds = transaction.getJSONArray("refundList"); if (refunds == null || refunds.isEmpty()) { return null; } long total = 0L; String lastTransactionId = null; for (int i = 0; i < refunds.size(); i++) { JSONObject refund = refunds.getJSONObject(i); Integer amount = refund == null ? null : refund.getInteger("refundAmount"); String transactionId = refund == null ? null : refund.getString("refundTransactionId"); if (amount == null || amount == 0 || transactionId == null) { return null; } total += Math.abs((long) amount); lastTransactionId = transactionId; } return total == expectedAmount ? lastTransactionId : null; } private static boolean hasRefundEvidence(JSONObject transaction) { JSONArray refunds = transaction.getJSONArray("refundList"); return refunds != null && !refunds.isEmpty(); } private static boolean canCheckOrConfirm(String status) { return "WAITING_AUTH".equals(status) || "READY_CONFIRM".equals(status); } private static String paymentProvider(LinePayResponse response) { if (response == null || response.rawBody() == null) { return null; } JSONObject root = JSONObject.parseObject(response.rawBody()); JSONObject objectInfo = root.getJSONObject("info"); if (objectInfo != null) { return objectInfo.getString("paymentProvider"); } JSONArray info = root.getJSONArray("info"); return info != null && info.size() == 1 ? info.getJSONObject(0).getString("paymentProvider") : null; } private static boolean confirmedPayment(LinePayResponse response, PosOrderLinePayment payment) { if (response == null || !response.isSuccess() || response.rawBody() == null) { return false; } JSONObject info = JSONObject.parseObject(response.rawBody()).getJSONObject("info"); if (info == null || !payment.getTransactionId().equals(info.getString("transactionId"))) { return false; } String orderId = info.getString("orderId"); String currency = info.getString("currency"); return (orderId == null || payment.getLineOrderId().equals(orderId)) && (currency == null || payment.getCurrency().equals(currency)) && payment.getAmount().equals(paymentAmount(info)); } private IntentSelection createIntent(PosOrder order, PosStoreLinePay credential) { for (int attempt = 0; attempt < 3; attempt++) { try { return new IntentSelection(paymentService.createRequesting( String.valueOf(order.getDdId()), newLineOrderId(), credential.getId(), order.getMdId(), order.getAmount(), "TWD", unknownDeadline()), true); } catch (DuplicateKeyException duplicate) { PosOrderLinePayment concurrent = paymentService.getActiveByDdId(String.valueOf(order.getDdId())); if (concurrent != null) { return new IntentSelection(concurrent, false); } if (attempt == 2) { throw duplicate; } } } throw new IllegalStateException("Unable to create LINE Pay attempt"); } private record IntentSelection(PosOrderLinePayment payment, boolean created) { } private PosOrder requirePayableOrder(Long userId, String ddId) { PosOrder order = requireOwnedOrder(userId, ddId); if (!PAY_TYPE_LINE.equals(order.getPayType()) || Long.valueOf(1L).equals(order.getPayStatus()) || !isPayableOrderState(order) || order.getAmount() == null || order.getAmount() <= 0 || order.getMdId() == null) { throw new ServiceException("Order is not payable by LINE Pay"); } return order; } private static boolean isPayableOrderState(PosOrder order) { return order.getState() != null && (order.getState() == 0L || order.getState() == 1L) && (order.getAfterSaleStatus() == null || order.getAfterSaleStatus() == 0L); } private static boolean isDefiniteRequestFailure(LinePayResponse response) { if (response == null || response.httpStatus() < 200 || response.httpStatus() >= 300 || response.returnCode() == null || response.isSuccess()) { return false; } return DEFINITE_REQUEST_FAILURE_CODES.contains(response.returnCode()); } private Date leaseUntil() { return new Date(System.currentTimeMillis() + Math.max(60L, rowLeaseSeconds) * 1000L); } private PosOrder requireOwnedOrder(Long userId, String ddId) { if (userId == null || ddId == null || ddId.trim().isEmpty()) { throw new ServiceException("Invalid order"); } PosOrder order = orderService.getOne(new QueryWrapper().eq("dd_id", ddId)); if (order == null || order.getUserId() == null || !order.getUserId().equals(userId)) { throw new ServiceException("Order not found or forbidden"); } return order; } private void requireSingleStoreOrder(PosOrder order) { String parentDdId = order.getParentDdId() == null ? String.valueOf(order.getDdId()) : order.getParentDdId(); long count = orderService.count(new QueryWrapper().eq("parent_dd_id", parentDdId)); if (count > 1) { throw new ServiceException("LINE Pay supports single-store orders only"); } } private PosOrderLinePayment selectForApp(PosOrder order, List attempts) { if (attempts == null || attempts.isEmpty()) { return null; } List paid = attempts.stream() .filter(payment -> "PAID".equals(payment.getStatus())).toList(); List unrefunded = paid.stream().filter(payment -> { PosOrderLineRefund refund = refundService.getByPaymentId(payment.getId()); return refund == null || !"REFUNDED".equals(refund.getStatus()); }).toList(); if (Long.valueOf(1L).equals(order.getPayStatus()) && unrefunded.size() == 1) { return unrefunded.get(0); } if (Long.valueOf(2L).equals(order.getPayStatus()) && !paid.isEmpty()) { return paid.stream().filter(payment -> { PosOrderLineRefund refund = refundService.getByPaymentId(payment.getId()); return refund != null && "REFUNDED".equals(refund.getStatus()); }).findFirst().orElse(paid.get(0)); } if (unrefunded.size() > 1) { PosOrderLinePayment manual = unrefunded.get(0); manual.setStatus("MANUAL_REVIEW"); return manual; } if (paid.size() == 1) { return paid.get(0); } return attempts.stream().filter(payment -> payment.getActiveDdId() != null).findFirst() .orElseGet(() -> attempts.stream().max(Comparator .comparing(PosOrderLinePayment::getCreateTime, Comparator.nullsFirst(Date::compareTo)) .thenComparing(PosOrderLinePayment::getId, Comparator.nullsFirst(Long::compareTo))).orElse(null)); } private static LinePayCredential toCredential(PosStoreLinePay credential) { return new LinePayCredential(credential.getId(), credential.getChannelId(), credential.getChannelSecret()); } private RetrieveResult retrieveWithCredentialRecovery(PosOrderLinePayment payment, String source, PosStoreLinePay originalVersion, LinePayCredential originalCredential) throws Exception { LinePayResponse original = retrieve(payment, source, originalCredential); if (!isCredentialAuthFailure(original)) { return new RetrieveResult(original, originalCredential); } PosStoreLinePay current = credentialService.getCurrent(payment.getStoreId()); if (current == null || Objects.equals(current.getId(), originalVersion.getId()) || !Objects.equals(current.getChannelId(), originalVersion.getChannelId()) || !Objects.equals(current.getEnvironment(), originalVersion.getEnvironment())) { return new RetrieveResult(original, originalCredential); } LinePayCredential currentCredential = toCredential(current); LinePayResponse proof = retrieve(payment, source, currentCredential); return capturedPayment(proof, payment) == null ? new RetrieveResult(original, originalCredential) : new RetrieveResult(proof, currentCredential); } private LinePayResponse retrieve(PosOrderLinePayment payment, String source, LinePayCredential credential) throws Exception { return gatewayCall("RETRIEVE", source, payment, credential.id(), () -> payment.getTransactionId() == null ? linePayClient.retrieveByOrderId(credential, payment.getLineOrderId()) : linePayClient.retrieveByTransactionId(credential, payment.getTransactionId())); } private static boolean isCredentialAuthFailure(LinePayResponse response) { return response != null && CREDENTIAL_AUTH_FAILURE_CODES.contains(response.returnCode()); } private boolean isLocallyRefunded(Long paymentId) { PosOrderLineRefund refund = refundService.getByPaymentId(paymentId); return refund != null && "REFUNDED".equals(refund.getStatus()); } private record RetrieveResult(LinePayResponse response, LinePayCredential credential) { } private static LinePayCreateResult createResult(PosOrderLinePayment payment, boolean reused) { LinePayCreateResult result = new LinePayCreateResult(); result.setDdId(payment.getDdId()); result.setPaymentId(payment.getId()); result.setLineOrderId(payment.getLineOrderId()); result.setTransactionId(payment.getTransactionId()); result.setPaymentUrl(payment.getPaymentUrlWeb()); result.setStatus(payment.getStatus()); result.setReusedAttempt(reused); return result; } private static String newLineOrderId() { return "LP" + UUID.randomUUID().toString().replace("-", "").toUpperCase(Locale.ROOT); } private T gatewayCall(String action, String source, PosOrderLinePayment payment, LinePayGatewayAuditService.GatewayCall call) throws Exception { return gatewayCall(action, source, payment, payment.getCredentialId(), call); } private T gatewayCall(String action, String source, PosOrderLinePayment payment, Long credentialId, LinePayGatewayAuditService.GatewayCall call) throws Exception { if (auditService == null) { return call.call(); } return auditService.execute(action, source == null ? "LINE" : source, payment.getId(), null, credentialId, payment.getStoreId(), payment.getDdId(), payment.getLineOrderId(), payment.getTransactionId(), call); } private static Date shortly() { return new Date(System.currentTimeMillis() + 30_000L); } private Date authDeadline() { return new Date(System.currentTimeMillis() + authDeadlineMinutes * 60L * 1000L); } private Date unknownDeadline() { return new Date(System.currentTimeMillis() + unknownDeadlineHours * 60L * 60L * 1000L); } }