Search in sources :

Example 1 with PaymentTransactionType

use of org.broadleafcommerce.common.payment.PaymentTransactionType in project BroadleafCommerce by BroadleafCommerce.

the class ValidateAndConfirmPaymentActivity method execute.

@Override
public ProcessContext<CheckoutSeed> execute(ProcessContext<CheckoutSeed> context) throws Exception {
    Order order = context.getSeedData().getOrder();
    Map<String, Object> rollbackState = new HashMap<>();
    // There are definitely enough payments on the order. We now need to confirm each unconfirmed payment on the order.
    // Unconfirmed payments could be added for things like gift cards and account credits; they are not actually
    // decremented from the user's account until checkout. This could also be used in some credit card processing
    // situations
    // Important: The payment.getAmount() must be the final amount that is going to be confirmed. If the order total
    // changed, the order payments need to be adjusted to reflect this and must add up to the order total.
    // This can happen in the case of PayPal Express or other hosted gateways where the unconfirmed payment comes back
    // to a review page, the customer selects shipping and the order total is adjusted.
    /**
     * This list contains the additional transactions that were created to confirm previously unconfirmed transactions
     * which can occur if you send credit card data directly to Broadleaf and rely on this activity to confirm
     * that transaction
     */
    Map<OrderPayment, PaymentTransaction> additionalTransactions = new HashMap<>();
    List<ResponseTransactionPair> failedTransactions = new ArrayList<>();
    // Used for the rollback handler; we want to make sure that we roll back transactions that have already been confirmed
    // as well as transactions that we are about to confirm here
    List<PaymentTransaction> confirmedTransactions = new ArrayList<>();
    /**
     * This is a subset of the additionalTransactions that contains the transactions that were confirmed in this activity
     */
    Map<OrderPayment, PaymentTransactionType> additionalConfirmedTransactions = new HashMap<>();
    for (OrderPayment payment : order.getPayments()) {
        if (payment.isActive()) {
            for (PaymentTransaction tx : payment.getTransactions()) {
                if (OrderPaymentStatus.UNCONFIRMED.equals(orderPaymentStatusService.determineOrderPaymentStatus(payment)) && PaymentTransactionType.UNCONFIRMED.equals(tx.getType())) {
                    if (LOG.isTraceEnabled()) {
                        LOG.trace("Transaction " + tx.getId() + " is not confirmed. Proceeding to confirm transaction.");
                    }
                    PaymentResponseDTO responseDTO = orderPaymentConfirmationStrategy.confirmTransaction(tx, context);
                    if (responseDTO == null) {
                        String msg = "Unable to 'confirm' the UNCONFIRMED Transaction with id: " + tx.getId() + ". " + "The ResponseDTO null. Please check your order payment" + "confirmation strategy implementation";
                        LOG.error(msg);
                        throw new CheckoutException(msg, context.getSeedData());
                    }
                    if (LOG.isTraceEnabled()) {
                        LOG.trace("Transaction Confirmation Raw Response: " + responseDTO.getRawResponse());
                    }
                    if (responseDTO.getAmount() == null || responseDTO.getPaymentTransactionType() == null) {
                        // Log an error, an exception will get thrown later as the payments won't add up.
                        LOG.error("The ResponseDTO returned from the Gateway does not contain either an Amount or Payment Transaction Type. " + "Please check your implementation");
                    }
                    // Create a new transaction that references its parent UNCONFIRMED transaction.
                    PaymentTransaction transaction = orderPaymentService.createTransaction();
                    transaction.setAmount(responseDTO.getAmount());
                    transaction.setRawResponse(responseDTO.getRawResponse());
                    transaction.setSuccess(responseDTO.isSuccessful());
                    transaction.setType(responseDTO.getPaymentTransactionType());
                    transaction.setParentTransaction(tx);
                    transaction.setOrderPayment(payment);
                    transaction.setAdditionalFields(responseDTO.getResponseMap());
                    transaction = orderPaymentService.save(transaction);
                    additionalTransactions.put(payment, transaction);
                    if (responseDTO.isSuccessful()) {
                        // if response is successful, attempt to create a customer payment token
                        createCustomerPaymentToken(transaction);
                        additionalConfirmedTransactions.put(payment, transaction.getType());
                    } else {
                        failedTransactions.add(new ResponseTransactionPair(responseDTO, transaction.getId()));
                    }
                } else if (PaymentTransactionType.AUTHORIZE.equals(tx.getType()) || PaymentTransactionType.AUTHORIZE_AND_CAPTURE.equals(tx.getType())) {
                    // attempt to create a customer payment token if payment is marked as tokenized
                    createCustomerPaymentToken(tx);
                    // After each transaction is confirmed, associate the new list of confirmed transactions to the rollback state. This has the added
                    // advantage of being able to invoke the rollback handler if there is an exception thrown at some point while confirming multiple
                    // transactions. This is outside of the transaction confirmation block in order to capture transactions
                    // that were already confirmed prior to this activity running
                    confirmedTransactions.add(tx);
                }
            }
        }
    }
    // regardless of an error in the workflow later.
    for (OrderPayment payment : order.getPayments()) {
        if (additionalTransactions.containsKey(payment)) {
            PaymentTransactionType confirmedType = null;
            if (additionalConfirmedTransactions.containsKey(payment)) {
                confirmedType = additionalConfirmedTransactions.get(payment);
            }
            payment.addTransaction(additionalTransactions.get(payment));
            payment = orderPaymentService.save(payment);
            if (confirmedType != null) {
                List<PaymentTransaction> types = payment.getTransactionsForType(confirmedType);
                if (types.size() == 1) {
                    confirmedTransactions.add(types.get(0));
                } else {
                    throw new IllegalArgumentException("There should only be one AUTHORIZE or AUTHORIZE_AND_CAPTURE transaction." + "There are more than one confirmed payment transactions for Order Payment:" + payment.getId());
                }
            }
        }
    }
    // Once all transactions have been confirmed, add them to the rollback state.
    // If an exception is thrown after this, the confirmed transactions will need to be voided or reversed
    // (based on the implementation requirements of the Gateway)
    rollbackState.put(ROLLBACK_TRANSACTIONS, confirmedTransactions);
    ActivityStateManagerImpl.getStateManager().registerState(this, context, getRollbackHandler(), rollbackState);
    // Handle the failed transactions (default implementation is to throw a new CheckoutException)
    if (!failedTransactions.isEmpty()) {
        handleUnsuccessfulTransactions(failedTransactions, context);
    }
    // Add authorize and authorize_and_capture transactions;
    // there should only be one or the other in the payment
    // Also add any pending transactions (as these are marked as being AUTH or CAPTURED later)
    Money paymentSum = new Money(BigDecimal.ZERO);
    for (OrderPayment payment : order.getPayments()) {
        if (payment.isActive()) {
            paymentSum = paymentSum.add(payment.getSuccessfulTransactionAmountForType(PaymentTransactionType.AUTHORIZE)).add(payment.getSuccessfulTransactionAmountForType(PaymentTransactionType.AUTHORIZE_AND_CAPTURE)).add(payment.getSuccessfulTransactionAmountForType(PaymentTransactionType.PENDING));
        }
    }
    if (paymentSum.lessThan(order.getTotal())) {
        throw new IllegalArgumentException("There are not enough payments to pay for the total order. The sum of " + "the payments is " + paymentSum.getAmount().toPlainString() + " and the order total is " + order.getTotal().getAmount().toPlainString());
    }
    // that as well. Currently there isn't really a concept for that
    return context;
}
Also used : Order(org.broadleafcommerce.core.order.domain.Order) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) OrderPayment(org.broadleafcommerce.core.payment.domain.OrderPayment) CheckoutException(org.broadleafcommerce.core.checkout.service.exception.CheckoutException) PaymentTransaction(org.broadleafcommerce.core.payment.domain.PaymentTransaction) Money(org.broadleafcommerce.common.money.Money) PaymentTransactionType(org.broadleafcommerce.common.payment.PaymentTransactionType) PaymentResponseDTO(org.broadleafcommerce.common.payment.dto.PaymentResponseDTO)

Aggregations

ArrayList (java.util.ArrayList)1 HashMap (java.util.HashMap)1 Money (org.broadleafcommerce.common.money.Money)1 PaymentTransactionType (org.broadleafcommerce.common.payment.PaymentTransactionType)1 PaymentResponseDTO (org.broadleafcommerce.common.payment.dto.PaymentResponseDTO)1 CheckoutException (org.broadleafcommerce.core.checkout.service.exception.CheckoutException)1 Order (org.broadleafcommerce.core.order.domain.Order)1 OrderPayment (org.broadleafcommerce.core.payment.domain.OrderPayment)1 PaymentTransaction (org.broadleafcommerce.core.payment.domain.PaymentTransaction)1