use of org.broadleafcommerce.core.payment.domain.OrderPayment in project BroadleafCommerce by BroadleafCommerce.
the class OnePageCheckoutProcessor method populateSectionViewStates.
/**
* This method is responsible of populating the variables necessary to draw the checkout page.
* This logic is highly dependent on your layout. If your layout does not follow the same flow
* as the HeatClinic demo, you will need to override with your own custom layout implementation
*
* @param localVars
*/
protected void populateSectionViewStates(Map<String, Object> localVars) {
boolean orderInfoPopulated = hasPopulatedOrderInfo(CartState.getCart());
boolean billingPopulated = hasPopulatedBillingAddress(CartState.getCart());
boolean shippingPopulated = hasPopulatedShippingAddress(CartState.getCart());
localVars.put("orderInfoPopulated", orderInfoPopulated);
localVars.put("billingPopulated", billingPopulated);
localVars.put("shippingPopulated", shippingPopulated);
// Logic to show/hide sections based on state of the order
// show all sections including header unless specifically hidden
// (e.g. hide shipping if no shippable items in order or hide billing section if the order payment doesn't need
// an address i.e. PayPal Express)
boolean showBillingInfoSection = true;
boolean showShippingInfoSection = true;
boolean showAllPaymentMethods = true;
boolean showPaymentMethodSection = true;
int numShippableFulfillmentGroups = calculateNumShippableFulfillmentGroups();
if (numShippableFulfillmentGroups == 0) {
showShippingInfoSection = false;
}
boolean orderContainsThirdPartyPayment = false;
boolean orderContainsUnconfirmedCreditCard = false;
OrderPayment unconfirmedCC = null;
if (CartState.getCart().getPayments() != null) {
for (OrderPayment payment : CartState.getCart().getPayments()) {
if (payment.isActive() && PaymentType.THIRD_PARTY_ACCOUNT.equals(payment.getType())) {
orderContainsThirdPartyPayment = true;
}
if (payment.isActive() && (PaymentType.CREDIT_CARD.equals(payment.getType()) && !PaymentGatewayType.TEMPORARY.equals(payment.getGatewayType()))) {
orderContainsUnconfirmedCreditCard = true;
unconfirmedCC = payment;
}
}
}
// Toggle the Payment Info Section based on what payments were applied to the order
// (e.g. Third Party Account (i.e. PayPal Express) or Gift Cards/Customer Credit)
Money orderTotalAfterAppliedPayments = CartState.getCart().getTotalAfterAppliedPayments();
if (orderContainsThirdPartyPayment || orderContainsUnconfirmedCreditCard) {
showBillingInfoSection = false;
showAllPaymentMethods = false;
} else if (orderTotalAfterAppliedPayments != null && orderTotalAfterAppliedPayments.isZero()) {
// If all the applied payments (e.g. gift cards) cover the entire amount
// we don't need to show all payment method options.
showAllPaymentMethods = false;
}
localVars.put("showBillingInfoSection", showBillingInfoSection);
localVars.put("showAllPaymentMethods", showAllPaymentMethods);
localVars.put("showPaymentMethodSection", showPaymentMethodSection);
localVars.put("orderContainsThirdPartyPayment", orderContainsThirdPartyPayment);
localVars.put("orderContainsUnconfirmedCreditCard", orderContainsUnconfirmedCreditCard);
localVars.put("unconfirmedCC", unconfirmedCC);
// The Sections are all initialized to INACTIVE view
List<CheckoutSectionDTO> drawnSections = new LinkedList<>();
CheckoutSectionDTO orderInfoSection = new CheckoutSectionDTO(CheckoutSectionViewType.ORDER_INFO, orderInfoPopulated);
CheckoutSectionDTO billingInfoSection = new CheckoutSectionDTO(CheckoutSectionViewType.BILLING_INFO, billingPopulated);
CheckoutSectionDTO shippingInfoSection = new CheckoutSectionDTO(CheckoutSectionViewType.SHIPPING_INFO, shippingPopulated);
CheckoutSectionDTO paymentInfoSection = new CheckoutSectionDTO(CheckoutSectionViewType.PAYMENT_INFO, false);
String orderInfoHelpMessage = (String) localVars.get("orderInfoHelpMessage");
String billingInfoHelpMessage = (String) localVars.get("billingInfoHelpMessage");
String shippingInfoHelpMessage = (String) localVars.get("shippingInfoHelpMessage");
// Add the Order Info Section
drawnSections.add(orderInfoSection);
// Add the Billing Section
if (showBillingInfoSection) {
billingInfoSection.setHelpMessage(orderInfoHelpMessage);
drawnSections.add(billingInfoSection);
}
// Add the Shipping Section
if (showShippingInfoSection) {
if (showBillingInfoSection) {
shippingInfoSection.setHelpMessage(billingInfoHelpMessage);
} else {
shippingInfoSection.setHelpMessage(orderInfoHelpMessage);
}
drawnSections.add(shippingInfoSection);
}
// Add the Payment Section
if (showShippingInfoSection) {
paymentInfoSection.setHelpMessage(shippingInfoHelpMessage);
} else if (showBillingInfoSection) {
paymentInfoSection.setHelpMessage(billingInfoHelpMessage);
} else {
paymentInfoSection.setHelpMessage(orderInfoHelpMessage);
}
drawnSections.add(paymentInfoSection);
// Logic to toggle state between form view, saved view, and inactive view
// This is dependent on the layout of your checkout form. Override this if layout is different.
// initialize first view to always be a FORM view
CheckoutSectionDTO firstSection = drawnSections.get(0);
firstSection.setState(CheckoutSectionStateType.FORM);
for (ListIterator<CheckoutSectionDTO> itr = drawnSections.listIterator(); itr.hasNext(); ) {
CheckoutSectionDTO previousSection = null;
if (itr.hasPrevious()) {
previousSection = drawnSections.get(itr.previousIndex());
}
CheckoutSectionDTO section = itr.next();
// if the previous section is populated, set this section to a Form View
if (previousSection != null && previousSection.isPopulated()) {
section.setState(CheckoutSectionStateType.FORM);
}
// If this sections is populated then set this section to the Saved View
if (section.isPopulated()) {
section.setState(CheckoutSectionStateType.SAVED);
}
// {@see DefaultPaymentGatewayCheckoutService where payments are invalidated on an unsuccessful transaction}
if (CheckoutSectionViewType.PAYMENT_INFO.equals(section.getView())) {
if (showBillingInfoSection && !billingPopulated) {
section.setState(CheckoutSectionStateType.INACTIVE);
section.setHelpMessage(billingInfoHelpMessage);
}
}
// Finally, if the edit button is explicitly clicked, set the section to Form View
BroadleafRequestContext blcContext = BroadleafRequestContext.getBroadleafRequestContext();
HttpServletRequest request = blcContext.getRequest();
boolean editOrderInfo = BooleanUtils.toBoolean(request.getParameter("edit-order-info"));
boolean editBillingInfo = BooleanUtils.toBoolean(request.getParameter("edit-billing"));
boolean editShippingInfo = BooleanUtils.toBoolean(request.getParameter("edit-shipping"));
if (CheckoutSectionViewType.ORDER_INFO.equals(section.getView()) && editOrderInfo) {
section.setState(CheckoutSectionStateType.FORM);
} else if (CheckoutSectionViewType.BILLING_INFO.equals(section.getView()) && editBillingInfo) {
section.setState(CheckoutSectionStateType.FORM);
} else if (CheckoutSectionViewType.SHIPPING_INFO.equals(section.getView()) && editShippingInfo) {
section.setState(CheckoutSectionStateType.FORM);
}
}
localVars.put("checkoutSectionDTOs", drawnSections);
}
use of org.broadleafcommerce.core.payment.domain.OrderPayment 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;
}
use of org.broadleafcommerce.core.payment.domain.OrderPayment in project BroadleafCommerce by BroadleafCommerce.
the class ValidateAndConfirmPaymentActivity method handleUnsuccessfulTransactions.
/**
* <p>
* Default implementation is to throw a generic CheckoutException which will be caught and displayed
* on the Checkout Page where the Customer can try again. In many cases, this is
* sufficient as it is usually recommended to display a generic Error Message to prevent
* Credit Card fraud.
*
* <p>
* The configured payment gateway may return a more specific error.
* Each gateway is different and will often times return different error codes based on the acquiring bank as well.
* In that case, you may override this method to decipher these errors
* and handle it appropriately based on your business requirements.
*/
protected void handleUnsuccessfulTransactions(List<ResponseTransactionPair> failedTransactions, ProcessContext<CheckoutSeed> context) throws Exception {
// The Response DTO was not successful confirming/authorizing a transaction.
String msg = "Attempting to confirm/authorize an UNCONFIRMED transaction on the order was unsuccessful.";
/**
* For each of the failed transactions we might need to register state with the rollback handler
*/
List<OrderPayment> invalidatedPayments = new ArrayList<>();
List<PaymentTransaction> failedTransactionsToRollBack = new ArrayList<>();
List<PaymentResponseDTO> failedResponses = new ArrayList<>();
for (ResponseTransactionPair responseTransactionPair : failedTransactions) {
PaymentTransaction tx = orderPaymentService.readTransactionById(responseTransactionPair.getTransactionId());
if (shouldRollbackFailedTransaction(responseTransactionPair)) {
failedTransactionsToRollBack.add(tx);
} else if (!invalidatedPayments.contains(tx.getOrderPayment())) {
paymentGatewayCheckoutService.markPaymentAsInvalid(tx.getOrderPayment().getId());
OrderPayment payment = orderPaymentService.save(tx.getOrderPayment());
invalidatedPayments.add(payment);
}
failedResponses.add(responseTransactionPair.getResponseDTO());
}
/**
* Even though the original transaction confirmation failed, there is still a possibility that we need to rollback
* the failure. The use case is in the case of fraud checks, some payment gateways complete the AUTHORIZE prior to
* executing the fraud check. Thus, the AUTHORIZE technically fails because of fraud but the user's card was still
* charged. This handles the case of rolling back the AUTHORIZE transaction in that case
*/
Map<String, Object> rollbackState = new HashMap<>();
rollbackState.put(ROLLBACK_TRANSACTIONS, failedTransactionsToRollBack);
context.getSeedData().getUserDefinedFields().put(FAILED_RESPONSES, failedResponses);
ActivityStateManagerImpl.getStateManager().registerState(this, context, getRollbackHandler(), rollbackState);
if (LOG.isErrorEnabled()) {
LOG.error(msg);
}
if (LOG.isTraceEnabled()) {
for (ResponseTransactionPair responseTransactionPair : failedTransactions) {
LOG.trace(responseTransactionPair.getResponseDTO().getRawResponse());
}
}
throw new CheckoutException(msg, context.getSeedData());
}
use of org.broadleafcommerce.core.payment.domain.OrderPayment in project BroadleafCommerce by BroadleafCommerce.
the class OrderPaymentConfirmationStrategyImpl method confirmTransactionInternal.
protected PaymentResponseDTO confirmTransactionInternal(PaymentTransaction tx, ProcessContext<CheckoutSeed> context, boolean isCheckout) throws PaymentException, WorkflowException, CheckoutException {
// Cannot confirm anything here if there is no provider
if (paymentConfigurationServiceProvider == null) {
String msg = "There are unconfirmed payment transactions on this payment but no payment gateway" + " configuration or transaction confirmation service configured";
LOG.error(msg);
throw new CheckoutException(msg, context.getSeedData());
}
OrderPayment payment = tx.getOrderPayment();
PaymentGatewayConfigurationService cfg = paymentConfigurationServiceProvider.getGatewayConfigurationService(tx.getOrderPayment().getGatewayType());
PaymentResponseDTO responseDTO = null;
// Auto-calculate totals and line items to send to the gateway when in a "Checkout Payment flow"
// (i.e. where the transaction is part of a final payment that is meant to be charged last at checkout: UNCONFIRMED -> AUTHORIZE or UNCONFIRMED -> AUTHORIZE_AND_CAPTURE)
// Note: the total for the request cannot be auto-calculated if the order contains multiple final payments (i.e. multiple credit cards)
PaymentRequestDTO confirmationRequest;
if (payment.isFinalPayment() && !orderContainsMultipleFinalPayments(payment.getOrder())) {
confirmationRequest = orderToPaymentRequestService.translatePaymentTransaction(payment.getAmount(), tx, true);
} else {
confirmationRequest = orderToPaymentRequestService.translatePaymentTransaction(payment.getAmount(), tx);
}
populateBillingAddressOnRequest(confirmationRequest, payment);
populateCustomerOnRequest(confirmationRequest, payment);
populateShippingAddressOnRequest(confirmationRequest, payment);
if (isCheckout && enablePendingPaymentsOnCheckoutConfirmation()) {
responseDTO = constructPendingTransaction(payment.getType(), payment.getGatewayType(), confirmationRequest);
} else {
if (PaymentType.CREDIT_CARD.equals(payment.getType())) {
// Handles the PCI-Compliant Scenario where you have an UNCONFIRMED CREDIT_CARD payment on the order.
// This can happen if you send the Credit Card directly to Broadleaf or you use a Digital Wallet solution like MasterPass.
// The Actual Credit Card PAN is stored in blSecurePU and will need to be sent to the Payment Gateway for processing.
populateCreditCardOnRequest(confirmationRequest, payment);
if (cfg.getConfiguration().isPerformAuthorizeAndCapture()) {
responseDTO = cfg.getTransactionService().authorizeAndCapture(confirmationRequest);
} else {
responseDTO = cfg.getTransactionService().authorize(confirmationRequest);
}
} else {
// This handles the THIRD_PARTY_ACCOUNT scenario (like PayPal Express Checkout) where
// the transaction just needs to be confirmed with the Gateway
responseDTO = cfg.getTransactionConfirmationService().confirmTransaction(confirmationRequest);
}
}
return responseDTO;
}
use of org.broadleafcommerce.core.payment.domain.OrderPayment in project BroadleafCommerce by BroadleafCommerce.
the class OrderImpl method createOrRetrieveCopyInstance.
@Override
public <G extends Order> CreateResponse<G> createOrRetrieveCopyInstance(MultiTenantCopyContext context) throws CloneNotSupportedException {
CreateResponse<G> createResponse = context.createOrRetrieveCopyInstance(this);
if (createResponse.isAlreadyPopulated()) {
return createResponse;
}
Order cloned = createResponse.getClone();
cloned.setCurrency(getCurrency());
cloned.setEmailAddress(emailAddress);
cloned.setLocale(getLocale());
cloned.setName(name);
cloned.setOrderNumber(orderNumber);
cloned.setTotalTax(getTotalTax());
cloned.setSubmitDate(submitDate);
cloned.setCustomer(customer);
cloned.setStatus(getStatus());
cloned.setTotalFulfillmentCharges(getTotalFulfillmentCharges());
cloned.setSubTotal(getSubTotal());
cloned.setTaxOverride(taxOverride == null ? null : taxOverride);
for (OrderItem entry : orderItems) {
OrderItem clonedEntry = entry.createOrRetrieveCopyInstance(context).getClone();
clonedEntry.setOrder(cloned);
cloned.getOrderItems().add(clonedEntry);
}
for (Map.Entry<Offer, OfferInfo> entry : additionalOfferInformation.entrySet()) {
Offer clonedOffer = entry.getKey().createOrRetrieveCopyInstance(context).getClone();
OfferInfo clonedEntry = entry.getValue().createOrRetrieveCopyInstance(context).getClone();
cloned.getAdditionalOfferInformation().put(clonedOffer, clonedEntry);
}
for (Map.Entry<String, OrderAttribute> entry : orderAttributes.entrySet()) {
OrderAttribute clonedAttribute = entry.getValue().createOrRetrieveCopyInstance(context).getClone();
clonedAttribute.setOrder(cloned);
cloned.getOrderAttributes().put(entry.getKey(), clonedAttribute);
}
for (FulfillmentGroup fulfillmentGroup : fulfillmentGroups) {
FulfillmentGroup fg = fulfillmentGroup.createOrRetrieveCopyInstance(context).getClone();
fg.setOrder(cloned);
cloned.getFulfillmentGroups().add(fg);
}
for (OrderPayment orderPayment : payments) {
if (orderPayment.isActive()) {
OrderPayment payment = orderPayment.createOrRetrieveCopyInstance(context).getClone();
payment.setOrder(cloned);
cloned.getPayments().add(payment);
}
}
for (OfferCode entry : addedOfferCodes) {
// We do not want to clone the offer code since that will cascade through offer. We only want to create
// the new relationship between this cloned order and the existing offer code.
cloned.getAddedOfferCodes().add(entry);
}
cloned.setTotal(total == null ? null : new Money(total));
return createResponse;
}
Aggregations