Package com.brienwheeler.svc.authorize_net.impl

Source Code of com.brienwheeler.svc.authorize_net.impl.CIMClientService

/*
* The MIT License (MIT)
*
* Copyright (c) 2013 Brien L. Wheeler (brienwheeler@yahoo.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.brienwheeler.svc.authorize_net.impl;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import com.brienwheeler.lib.monitor.work.MonitoredWork;
import com.brienwheeler.lib.svc.GracefulShutdown;
import net.authorize.Environment;
import net.authorize.Merchant;
import net.authorize.ResponseCode;
import net.authorize.ResponseField;
import net.authorize.ResponseReasonCode;
import net.authorize.cim.Result;
import net.authorize.cim.Transaction;
import net.authorize.cim.TransactionType;
import net.authorize.cim.ValidationModeType;
import net.authorize.data.Order;
import net.authorize.data.cim.CustomerProfile;
import net.authorize.data.cim.DirectResponse;
import net.authorize.data.cim.PaymentProfile;
import net.authorize.data.cim.PaymentTransaction;
import net.authorize.data.xml.Payment;
import net.authorize.util.BasicXmlDocument;
import net.authorize.xml.Message;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.EntityBuilder;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.brienwheeler.lib.db.DbValidationUtils;
import com.brienwheeler.lib.db.TransactionWrapper;
import com.brienwheeler.lib.db.domain.DbId;
import com.brienwheeler.lib.util.OperationDisallowedException;
import com.brienwheeler.svc.authorize_net.AmountTooLargeException;
import com.brienwheeler.svc.authorize_net.AuthorizeNetException;
import com.brienwheeler.svc.authorize_net.CardExpiredException;
import com.brienwheeler.svc.authorize_net.ICIMClientService;
import com.brienwheeler.svc.authorize_net.InvalidCardAddressException;
import com.brienwheeler.svc.authorize_net.InvalidCardCodeException;
import com.brienwheeler.svc.authorize_net.InvalidCardNumberException;
import com.brienwheeler.svc.authorize_net.PaymentDeclinedException;
import com.brienwheeler.svc.authorize_net.PaymentMethod;
import com.brienwheeler.svc.users.IUserAttributeService;
import com.brienwheeler.svc.users.IUserService;
import com.brienwheeler.svc.users.domain.User;

public class CIMClientService extends AuthorizeNetClientBase implements ICIMClientService
{
  private static final String PRODUCTION_URL = "https://api.authorize.net/xml/v1/request.api";
  private static final String TEST_URL = "https://apitest.authorize.net/xml/v1/request.api";
 
  private static final String ELEMENT_TOKEN_OPEN = "<token>";
  private static final String ELEMENT_TOKEN_CLOSE = "</token>";
 
  private static final Map<ResponseReasonCode,Class<? extends AuthorizeNetException>> exceptionMap =
      new HashMap<ResponseReasonCode,Class<? extends AuthorizeNetException>>();

  private IUserAttributeService userAttributeService;
  private IUserService userService;
  private TransactionWrapper transactionWrapper;
 
  private Environment environment;
  private Merchant merchant;
  private String apiLoginID;
  private String transactionKey;
  private ValidationModeType validationMode = ValidationModeType.NONE;

  static {
    exceptionMap.put(ResponseReasonCode.RRC_2_27, InvalidCardAddressException.class);
    exceptionMap.put(ResponseReasonCode.RRC_2_37, InvalidCardNumberException.class);
    exceptionMap.put(ResponseReasonCode.RRC_2_44, InvalidCardCodeException.class);
    exceptionMap.put(ResponseReasonCode.RRC_2_65, InvalidCardCodeException.class);
    exceptionMap.put(ResponseReasonCode.RRC_2_127, InvalidCardAddressException.class);
    exceptionMap.put(ResponseReasonCode.RRC_2_315, InvalidCardNumberException.class);
    exceptionMap.put(ResponseReasonCode.RRC_2_317, CardExpiredException.class);
    exceptionMap.put(ResponseReasonCode.RRC_3_6, InvalidCardNumberException.class);
    exceptionMap.put(ResponseReasonCode.RRC_3_8, CardExpiredException.class);
    exceptionMap.put(ResponseReasonCode.RRC_3_49, AmountTooLargeException.class);
    exceptionMap.put(ResponseReasonCode.RRC_3_78, InvalidCardCodeException.class);
  }
 
  @Override
  protected void onStart() throws InterruptedException
  {
    super.onStart();
    merchant = Merchant.createMerchant(environment, apiLoginID, transactionKey);
  }

  @Override
  public boolean isProduction()
  {
    return ! merchant.isSandboxEnvironment();
  }
 
  @Override
  @MonitoredWork
    @GracefulShutdown
  // the write logic inside this function is in its own new transaction, so the interceptor can
  // treat this as a readOnly transaction
  @Transactional(readOnly=true, propagation=Propagation.SUPPORTS)
  public String createCustomerProfile(DbId<User> userId)
  {
    String existingCustomerProfileId = userAttributeService.getAttribute(userId, ATTR_PROFILE_ID);
    if (existingCustomerProfileId != null)
      return existingCustomerProfileId;
   
    final User user = userService.findById(userId);
    DbValidationUtils.assertPersisted(user);
   
    CustomerProfile customerProfile = CustomerProfile.createCustomerProfile();
    customerProfile.setMerchantCustomerId(Long.toString(userId.getId()));

    Transaction transaction = createTransaction(TransactionType.CREATE_CUSTOMER_PROFILE);
    transaction.setCustomerProfile(customerProfile);

    Result<Transaction> result = executeTransaction("create profile", userId, transaction);
    final String createdCustomerProfileId = result.getCustomerProfileId();
    log.info("created Authorize.Net customer profile " + createdCustomerProfileId + " for " + user);
   
    // we want to commit our own transaction to prevent the record from being created at Authorize.Net
    // and then an exception in a calling function that might have a transaction open preventing
    // the save of the UserAttribute recording the customer profile ID
    return transactionWrapper.doInNewWriteTransaction(new Callable<String>() {
      @Override
      public String call() throws Exception
      {
        try {
          userAttributeService.setAttribute(user, ATTR_PROFILE_ID, createdCustomerProfileId);
        }
        catch (RuntimeException e) {
          cleanupProfileId(createdCustomerProfileId);
          throw e;
        }
        catch (Error e) {
          cleanupProfileId(createdCustomerProfileId);
          throw e;
        }
        return createdCustomerProfileId;
      }
    });
  }
 
  @Override
  @MonitoredWork
    @GracefulShutdown
  @Transactional(readOnly=true, propagation=Propagation.SUPPORTS)
  public List<PaymentMethod> getPaymentMethods(DbId<User> userId)
  {
    String customerProfileId = userAttributeService.getAttribute(userId, ATTR_PROFILE_ID);
    if (customerProfileId == null)
      return new ArrayList<PaymentMethod>();
    return getPaymentMethods(userId, customerProfileId);
  }
 
  private List<PaymentMethod> getPaymentMethods(DbId<User> userId, String customerProfileId)
  {
    Transaction transaction = createTransaction(TransactionType.GET_CUSTOMER_PROFILE);
    transaction.setCustomerProfileId(customerProfileId);
   
    Result<Transaction> result = executeTransaction("get payment methods", userId, transaction);
   
    List<PaymentMethod> paymentMethods = new ArrayList<PaymentMethod>();
    for (PaymentProfile paymentProfile : result.getCustomerPaymentProfileList()) {
      for (Payment payment : paymentProfile.getPaymentList()) {
        if (payment.getCreditCard() != null) {
          paymentMethods.add(new PaymentMethod(paymentProfile.getCustomerPaymentProfileId(),
              payment.getCreditCard().getCreditCardNumber()));
        }
      }
    }
   
    return paymentMethods;
  }
 
  @Override
  @MonitoredWork
    @GracefulShutdown
  @Transactional//(readOnly=true, propagation=Propagation.SUPPORTS)
  public String getHostedProfilePageToken(DbId<User> userId, String returnUrl)
  {
    // More than two years later this still isn't in their Java SDK.  Oh well, let's just do it
    // the stupid way...
   
    String customerProfileId = userAttributeService.getAttribute(userId, ATTR_PROFILE_ID);
    if (customerProfileId == null)
      customerProfileId = createCustomerProfile(userId);

    StringBuffer buffer = new StringBuffer(4096);
    buffer.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
    buffer.append("<getHostedProfilePageRequest xmlns=\"AnetApi/xml/v1/schema/AnetApiSchema.xsd\">\n");
    buffer.append("  <merchantAuthentication>\n");
    buffer.append("    <name>" + apiLoginID + "</name>");
    buffer.append("    <transactionKey>" + transactionKey + "</transactionKey>\n");
    buffer.append("  </merchantAuthentication>\n");
    buffer.append("  <customerProfileId>" + customerProfileId + "</customerProfileId> \n");
    buffer.append("  <hostedProfileSettings>\n");
    buffer.append("    <setting>\n");
    buffer.append("      <settingName>hostedProfileReturnUrl</settingName>\n");
    buffer.append("      <settingValue>" + returnUrl + "</settingValue>\n");
    buffer.append("    </setting>\n");
    buffer.append("  </hostedProfileSettings>\n");
    buffer.append("</getHostedProfilePageRequest>\n");
 
    CloseableHttpClient httpClient = HttpClients.createDefault();
    HttpPost httpPost = new HttpPost(merchant.isSandboxEnvironment() ? TEST_URL : PRODUCTION_URL);
    EntityBuilder entityBuilder = EntityBuilder.create();
    entityBuilder.setContentType(ContentType.TEXT_XML);
    entityBuilder.setContentEncoding("utf-8");
    entityBuilder.setText(buffer.toString());
    httpPost.setEntity(entityBuilder.build());
   
    try {
      CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
      String response = EntityUtils.toString(httpResponse.getEntity());
      int start  = response.indexOf(ELEMENT_TOKEN_OPEN);
      if (start == -1)
        throw new AuthorizeNetException("error fetching hosted profile page token for " + userId + ", response: " + response);
      int end  = response.indexOf(ELEMENT_TOKEN_CLOSE);
      if (end == -1)
        throw new AuthorizeNetException("error fetching hosted profile page token for " + userId + ", response: " + response);
      return response.substring(start + ELEMENT_TOKEN_OPEN.length(), end);
       
    }
    catch (ClientProtocolException e) {
      throw new AuthorizeNetException(e.getMessage(), e);
    }
    catch (IOException e) {
      throw new AuthorizeNetException(e.getMessage(), e);
    }
  }
 
  @Override
  public String authorizePayment(DbId<User> userId, String paymentProfileId, BigDecimal amountToAuthorize)
  {
    return authorizePayment(userId, paymentProfileId, null, amountToAuthorize);
  }
 
  @Override
  @MonitoredWork
    @GracefulShutdown
  public String authorizePayment(DbId<User> userId, String paymentProfileId, String cardCode, BigDecimal amountToAuthorize)
  {
    String customerProfileId = userAttributeService.getAttribute(userId, ATTR_PROFILE_ID);
    if (customerProfileId == null)
      throw new OperationDisallowedException(userId + " has no Authorize.Net customer profile");
   
    List<PaymentMethod> paymentMethods = getPaymentMethods(userId, customerProfileId);
    PaymentMethod paymentMethodToUse = null;
    for (PaymentMethod paymentMethod : paymentMethods)
      if (paymentMethod.getPaymentProfileId().equals(paymentProfileId)) {
        paymentMethodToUse = paymentMethod;
        break;
      }
    if (paymentMethodToUse == null)
      throw new OperationDisallowedException(userId + " does not have paymentProfileId " + paymentProfileId);

    Order order = Order.createOrder();
    order.setTotalAmount(amountToAuthorize);

    PaymentTransaction paymentTransaction = PaymentTransaction.createPaymentTransaction();
    paymentTransaction.setTransactionType(net.authorize.TransactionType.AUTH_ONLY);
    paymentTransaction.setCustomerPaymentProfileId(paymentProfileId);
    paymentTransaction.setOrder(order);
    if (cardCode != null)
      paymentTransaction.setCardCode(cardCode);
   
    Transaction transaction = createTransaction(TransactionType.CREATE_CUSTOMER_PROFILE_TRANSACTION);
    transaction.setPaymentTransaction(paymentTransaction);
    transaction.setCustomerProfileId(customerProfileId);
   
    Result<Transaction> result = executeTransaction("authorize", userId, amountToAuthorize, transaction);

    Map<ResponseField,String> responseMap = result.getDirectResponseList().get(0).getDirectResponseMap();
    ResponseReasonCode responseReasonCode = ResponseReasonCode.findByReasonCode(responseMap.get(ResponseField.RESPONSE_REASON_CODE));
    switch (responseReasonCode) {
      case RRC_1_1:
        log.info("successfully authorized payment of " + amountToAuthorize + " for " + userId + ": " + responseReasonCode);
        return responseMap.get(ResponseField.TRANSACTION_ID);
      case RRC_4_253:
        log.info("successfully authorized (but held for review) payment of " + amountToAuthorize + " for " + userId + ": " + responseReasonCode);
        return responseMap.get(ResponseField.TRANSACTION_ID);
      default :
        log.info("authorization failed in amount of " + amountToAuthorize + " for " + userId + ": " + responseReasonCode);
        throw new AuthorizeNetException(responseReasonCode.getReasonText());
    }
  }
 
  @Override
  @MonitoredWork
    @GracefulShutdown
  public String settlePayment(DbId<User> userId, String transactionId, BigDecimal amountToSettle)
  {
    String customerProfileId = userAttributeService.getAttribute(userId, ATTR_PROFILE_ID);
    if (customerProfileId == null)
      throw new OperationDisallowedException(userId + " has no Authorize.Net customer profile");
   
    Order order = Order.createOrder();
    order.setTotalAmount(amountToSettle);

    PaymentTransaction paymentTransaction = PaymentTransaction.createPaymentTransaction();
    paymentTransaction.setTransactionType(net.authorize.TransactionType.PRIOR_AUTH_CAPTURE);
    paymentTransaction.setTransactionId(transactionId);
    paymentTransaction.setOrder(order);

    Transaction transaction = createTransaction(TransactionType.CREATE_CUSTOMER_PROFILE_TRANSACTION);
    transaction.setPaymentTransaction(paymentTransaction);
    transaction.setCustomerProfileId(customerProfileId);
   
    Result<Transaction> result = executeTransaction("settle", userId, amountToSettle,transaction);

    Map<ResponseField,String> responseMap = result.getDirectResponseList().get(0).getDirectResponseMap();
    ResponseReasonCode responseReasonCode = ResponseReasonCode.findByReasonCode(responseMap.get(ResponseField.RESPONSE_REASON_CODE));
    if (responseReasonCode == ResponseReasonCode.RRC_1_1) {
      log.info("successfully settled payment of " + amountToSettle + " for " + userId);
      return responseMap.get(ResponseField.TRANSACTION_ID);
    }
    else
      throw new AuthorizeNetException(responseReasonCode.getReasonText());
  }
 
  private void cleanupProfileId(String customerProfileId)
  {
    Transaction transaction = createTransaction(TransactionType.DELETE_CUSTOMER_PROFILE);
    transaction.setCustomerProfileId(customerProfileId);
    BasicXmlDocument response = net.authorize.util.HttpClient.executeXML(environment, transaction);
    Result<Transaction> result = Result.createResult(transaction, response);
    if (!result.isOk()) {
      recordInterventionRequest("failed to clean up Authorize.Net customer profile id " + customerProfileId + " " +
          createErrorMessage(result));
    }
  }
 
  private Transaction createTransaction(TransactionType transactionType)
  {
    Transaction transaction = merchant.createCIMTransaction(transactionType);
    transaction.setValidationMode(validationMode);
    return transaction;
  }
 
  private Result<Transaction> executeTransaction(String logOperation, DbId<User> userId, Transaction transaction)
  {
    return executeTransaction(logOperation, userId, new BigDecimal(0), transaction);
  }
 
  private Result<Transaction> executeTransaction(String logOperation, DbId<User> userId, BigDecimal amount, Transaction transaction)
  {
    BasicXmlDocument response = net.authorize.util.HttpClient.executeXML(environment, transaction);

    if (log.isInfoEnabled()) {
      BasicXmlDocument request = transaction.getCurrentRequest();
      if (request != null) {
        log.info(request.dump());
        log.info(response.dump());
      }
    }
   
    Result<Transaction> result = Result.createResult(transaction, response);
   
    List<DirectResponse> directResponses = result.getDirectResponseList();
    // check ResponseReasonCode to see if it means we should throw an exception
    if (directResponses != null && directResponses.size() > 0) {
      Map<ResponseField,String> directResponseMap = directResponses.get(0).getDirectResponseMap();
      ResponseCode responseCode = ResponseCode.findByResponseCode(directResponseMap.get(ResponseField.RESPONSE_CODE));
      ResponseReasonCode responseReasonCode = ResponseReasonCode.findByReasonCode(directResponseMap.get(ResponseField.RESPONSE_REASON_CODE));

      // check exceptionMap to see if we should throw specific exception
      Class<? extends AuthorizeNetException> exceptionClass = exceptionMap.get(responseReasonCode);
      if (exceptionClass != null) {
        log.info(logOperation + " failed in amount of " + amount + " for " + userId + ": " + responseReasonCode);
        try {
          Constructor<? extends AuthorizeNetException> constructor = exceptionClass.getConstructor(String.class);
          throw constructor.newInstance(responseReasonCode.getReasonText());
        }
        catch (NoSuchMethodException e) { /* fall through */ }
        catch (InvocationTargetException e) { /* fall through */ }
        catch (IllegalAccessException e) { /* fall through */ }
        catch (InstantiationException e) { /* fall through */ }
        log.warn("Exception class " + exceptionClass.getSimpleName() + " failed reflection instantiation");
        // we know we want an exception but failed to instantiate it.  Throw ANE as default
        throw new AuthorizeNetException(responseReasonCode.getReasonText());
      }

      // umbrella processing for DECLINED
      if (responseCode == ResponseCode.DECLINED) {
        log.info(logOperation + " failed in amount of " + amount + " for " + userId + ": " + responseReasonCode);
        throw new PaymentDeclinedException(responseReasonCode.getReasonText());
      }
    }
   
    // map all other failures (where no DirectResponseMap or RRC may not be in exceptionMap) into ANE
    if (!result.isOk()) {
      String message = createErrorMessage(result);
      log.warn(logOperation + " failed for " + userId + ": " + message);
      throw new AuthorizeNetException(message);
    }
   
    return result;
  }
 
  private String createErrorMessage(Result<Transaction> result)
  {
    StringBuffer buffer = new StringBuffer();
    buffer.append(result.getResultCode());
    for (Message message : result.getMessages()) {
      buffer.append(" (");
      buffer.append(message.getCode());
      buffer.append(":");
      buffer.append(message.getText());
      buffer.append(")");
    }
    return buffer.toString();
  }
 
  @Required
  public void setUserService(IUserService userService)
  {
    this.userService = userService;
  }

  @Required
  public void setUserAttributeService(IUserAttributeService userAttributeService)
  {
    this.userAttributeService = userAttributeService;
  }

  @Required
  public void setEnvironment(String environment)
  {
    this.environment = Environment.valueOf(environment);
  }

  @Required
  public void setApiLoginID(String apiLoginID)
  {
    this.apiLoginID = apiLoginID;
  }

  @Required
  public void setTransactionKey(String transactionKey)
  {
    this.transactionKey = transactionKey;
  }

  public void setValidationMode(String validationMode)
  {
    this.validationMode = ValidationModeType.valueOf(validationMode);
  }

  @Required
  public void setTransactionWrapper(TransactionWrapper transactionWrapper)
  {
    this.transactionWrapper = transactionWrapper;
  }
 
}
TOP

Related Classes of com.brienwheeler.svc.authorize_net.impl.CIMClientService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.