| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685 |
- 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<String> DEFINITE_REQUEST_FAILURE_CODES = List.of(
- "1104", "1105", "1106", "1124", "1178", "1183", "1194", "2101", "2102");
- private static final Set<String> 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<PosOrderLinePayment> 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<PosOrder>().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<PosOrder>().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<PosOrder>().eq("parent_dd_id", parentDdId));
- if (count > 1) {
- throw new ServiceException("LINE Pay supports single-store orders only");
- }
- }
- private PosOrderLinePayment selectForApp(PosOrder order, List<PosOrderLinePayment> attempts) {
- if (attempts == null || attempts.isEmpty()) {
- return null;
- }
- List<PosOrderLinePayment> paid = attempts.stream()
- .filter(payment -> "PAID".equals(payment.getStatus())).toList();
- List<PosOrderLinePayment> 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> T gatewayCall(String action, String source, PosOrderLinePayment payment,
- LinePayGatewayAuditService.GatewayCall<T> call) throws Exception {
- return gatewayCall(action, source, payment, payment.getCredentialId(), call);
- }
- private <T> T gatewayCall(String action, String source, PosOrderLinePayment payment,
- Long credentialId,
- LinePayGatewayAuditService.GatewayCall<T> 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);
- }
- }
|