package com.salesmanager.core.business.modules.integration.payment.impl;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.salesmanager.core.business.utils.ProductPriceUtils;
import com.salesmanager.core.model.customer.Customer;
import com.salesmanager.core.model.merchant.MerchantStore;
import com.salesmanager.core.model.order.Order;
import com.salesmanager.core.model.payments.Payment;
import com.salesmanager.core.model.payments.PaymentType;
import com.salesmanager.core.model.payments.Transaction;
import com.salesmanager.core.model.payments.TransactionType;
import com.salesmanager.core.model.shoppingcart.ShoppingCartItem;
import com.salesmanager.core.model.system.IntegrationConfiguration;
import com.salesmanager.core.model.system.IntegrationModule;
import com.salesmanager.core.modules.integration.IntegrationException;
import com.salesmanager.core.modules.integration.payment.model.PaymentModule;
import com.stripe.Stripe;
import com.stripe.exception.APIConnectionException;
import com.stripe.exception.AuthenticationException;
import com.stripe.exception.CardException;
import com.stripe.exception.InvalidRequestException;
import com.stripe.exception.StripeException;
import com.stripe.model.Charge;
import com.stripe.model.Refund;
public class StripePayment implements PaymentModule {
@Inject
private ProductPriceUtils productPriceUtils;
private final static String AUTHORIZATION = "Authorization";
private final static String TRANSACTION = "Transaction";
private static final Logger LOGGER = LoggerFactory.getLogger(StripePayment.class);
@Override
public void validateModuleConfiguration(
IntegrationConfiguration integrationConfiguration,
MerchantStore store) throws IntegrationException {
List<String> errorFields = null;
Map<String,String> keys = integrationConfiguration.getIntegrationKeys();
//validate integrationKeys['secretKey']
if(keys==null || StringUtils.isBlank(keys.get("secretKey"))) {
errorFields = new ArrayList<String>();
errorFields.add("secretKey");
}
//validate integrationKeys['publishableKey']
if(keys==null || StringUtils.isBlank(keys.get("publishableKey"))) {
if(errorFields==null) {
errorFields = new ArrayList<String>();
}
errorFields.add("publishableKey");
}
if(errorFields!=null) {
IntegrationException ex = new IntegrationException(IntegrationException.ERROR_VALIDATION_SAVE);
ex.setErrorFields(errorFields);
throw ex;
}
}
@Override
public Transaction initTransaction(MerchantStore store, Customer customer,
BigDecimal amount, Payment payment,
IntegrationConfiguration configuration, IntegrationModule module)
throws IntegrationException {
// Not supported
return null;
}
@Override
public Transaction authorize(MerchantStore store, Customer customer,
List<ShoppingCartItem> items, BigDecimal amount, Payment payment,
IntegrationConfiguration configuration, IntegrationModule module)
throws IntegrationException {
Transaction transaction = new Transaction();
try {
String apiKey = configuration.getIntegrationKeys().get("secretKey");
if(payment.getPaymentMetaData()==null || StringUtils.isBlank(apiKey)) {
IntegrationException te = new IntegrationException(
"Can't process Stripe, missing payment.metaData");
te.setExceptionType(IntegrationException.TRANSACTION_EXCEPTION);
te.setMessageCode("message.payment.error");
te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION);
throw te;
}
String token = payment.getPaymentMetaData().get("stripe_token");
if(StringUtils.isBlank(token)) {
IntegrationException te = new IntegrationException(
"Can't process Stripe, missing stripe token");
te.setExceptionType(IntegrationException.TRANSACTION_EXCEPTION);
te.setMessageCode("message.payment.error");
te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION);
throw te;
}
String amnt = productPriceUtils.getAdminFormatedAmount(store, amount);
//stripe does not support floating point
//so amnt * 100 or remove floating point
//553.47 = 55347
String strAmount = String.valueOf(amnt);
strAmount = strAmount.replace(".","");
Map<String, Object> chargeParams = new HashMap<String, Object>();
chargeParams.put("amount", strAmount);
chargeParams.put("capture", false);
chargeParams.put("currency", store.getCurrency().getCode());
chargeParams.put("source", token); // obtained with Stripe.js
chargeParams.put("description", new StringBuilder().append(TRANSACTION).append(" - ").append(store.getStorename()).toString());
Stripe.apiKey = apiKey;
Charge ch = Charge.create(chargeParams);
//Map<String,String> metadata = ch.getMetadata();
transaction.setAmount(amount);
//transaction.setOrder(order);
transaction.setTransactionDate(new Date());
transaction.setTransactionType(TransactionType.AUTHORIZE);
transaction.setPaymentType(PaymentType.CREDITCARD);
transaction.getTransactionDetails().put("TRANSACTIONID", token);
transaction.getTransactionDetails().put("TRNAPPROVED", ch.getStatus());
transaction.getTransactionDetails().put("TRNORDERNUMBER", ch.getId());
transaction.getTransactionDetails().put("MESSAGETEXT", null);
} catch (Exception e) {
throw buildException(e);
}
return transaction;
}
@Override
public Transaction capture(MerchantStore store, Customer customer,
Order order, Transaction capturableTransaction,
IntegrationConfiguration configuration, IntegrationModule module)
throws IntegrationException {
Transaction transaction = new Transaction();
try {
String apiKey = configuration.getIntegrationKeys().get("secretKey");
if(StringUtils.isBlank(apiKey)) {
IntegrationException te = new IntegrationException(
"Can't process Stripe, missing payment.metaData");
te.setExceptionType(IntegrationException.TRANSACTION_EXCEPTION);
te.setMessageCode("message.payment.error");
te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION);
throw te;
}
String chargeId = capturableTransaction.getTransactionDetails().get("TRNORDERNUMBER");
if(StringUtils.isBlank(chargeId)) {
IntegrationException te = new IntegrationException(
"Can't process Stripe capture, missing TRNORDERNUMBER");
te.setExceptionType(IntegrationException.TRANSACTION_EXCEPTION);
te.setMessageCode("message.payment.error");
te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION);
throw te;
}
Stripe.apiKey = apiKey;
Charge ch = Charge.retrieve(chargeId);
ch.capture();
transaction.setAmount(order.getTotal());
transaction.setOrder(order);
transaction.setTransactionDate(new Date());
transaction.setTransactionType(TransactionType.CAPTURE);
transaction.setPaymentType(PaymentType.CREDITCARD);
transaction.getTransactionDetails().put("TRANSACTIONID", capturableTransaction.getTransactionDetails().get("TRANSACTIONID"));
transaction.getTransactionDetails().put("TRNAPPROVED", ch.getStatus());
transaction.getTransactionDetails().put("TRNORDERNUMBER", ch.getId());
transaction.getTransactionDetails().put("MESSAGETEXT", null);
//authorize a preauth
return transaction;
} catch (Exception e) {
throw buildException(e);
}
}
@Override
public Transaction authorizeAndCapture(MerchantStore store, Customer customer,
List<ShoppingCartItem> items, BigDecimal amount, Payment payment,
IntegrationConfiguration configuration, IntegrationModule module)
throws IntegrationException {
String apiKey = configuration.getIntegrationKeys().get("secretKey");
if(payment.getPaymentMetaData()==null || StringUtils.isBlank(apiKey)) {
IntegrationException te = new IntegrationException(
"Can't process Stripe, missing payment.metaData");
te.setExceptionType(IntegrationException.TRANSACTION_EXCEPTION);
te.setMessageCode("message.payment.error");
te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION);
throw te;
}
String token = payment.getPaymentMetaData().get("stripe_token");
if(StringUtils.isBlank(token)) {
IntegrationException te = new IntegrationException(
"Can't process Stripe, missing stripe token");
te.setExceptionType(IntegrationException.TRANSACTION_EXCEPTION);
te.setMessageCode("message.payment.error");
te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION);
throw te;
}
Transaction transaction = new Transaction();
try {
String amnt = productPriceUtils.getAdminFormatedAmount(store, amount);
//stripe does not support floating point
//so amnt * 100 or remove floating point
//553.47 = 55347
String strAmount = String.valueOf(amnt);
strAmount = strAmount.replace(".","");
Map<String, Object> chargeParams = new HashMap<String, Object>();
chargeParams.put("amount", strAmount);
chargeParams.put("capture", true);
chargeParams.put("currency", store.getCurrency().getCode());
chargeParams.put("source", token); // obtained with Stripe.js
chargeParams.put("description", new StringBuilder().append(TRANSACTION).append(" - ").append(store.getStorename()).toString());
Stripe.apiKey = apiKey;
Charge ch = Charge.create(chargeParams);
//Map<String,String> metadata = ch.getMetadata();
transaction.setAmount(amount);
//transaction.setOrder(order);
transaction.setTransactionDate(new Date());
transaction.setTransactionType(TransactionType.AUTHORIZE);
transaction.setPaymentType(PaymentType.CREDITCARD);
transaction.getTransactionDetails().put("TRANSACTIONID", token);
transaction.getTransactionDetails().put("TRNAPPROVED", ch.getStatus());
transaction.getTransactionDetails().put("TRNORDERNUMBER", ch.getId());
transaction.getTransactionDetails().put("MESSAGETEXT", null);
} catch (Exception e) {
throw buildException(e);
}
return transaction;
}
@Override
public Transaction refund(boolean partial, MerchantStore store, Transaction transaction,
Order order, BigDecimal amount,
IntegrationConfiguration configuration, IntegrationModule module)
throws IntegrationException {
String apiKey = configuration.getIntegrationKeys().get("secretKey");
if(StringUtils.isBlank(apiKey)) {
IntegrationException te = new IntegrationException(
"Can't process Stripe, missing payment.metaData");
te.setExceptionType(IntegrationException.TRANSACTION_EXCEPTION);
te.setMessageCode("message.payment.error");
te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION);
throw te;
}
try {
String trnID = transaction.getTransactionDetails().get("TRNORDERNUMBER");
String amnt = productPriceUtils.getAdminFormatedAmount(store, amount);
Stripe.apiKey = apiKey;
//stripe does not support floating point
//so amnt * 100 or remove floating point
//553.47 = 55347
String strAmount = String.valueOf(amnt);
strAmount = strAmount.replace(".","");
Map params = new HashMap();
//TODO amount
params.put("amount", strAmount);
Charge ch = Charge.retrieve(trnID);
Refund re = ch.getRefunds().create(params);
transaction = new Transaction();
transaction.setAmount(order.getTotal());
transaction.setOrder(order);
transaction.setTransactionDate(new Date());
transaction.setTransactionType(TransactionType.CAPTURE);
transaction.setPaymentType(PaymentType.CREDITCARD);
transaction.getTransactionDetails().put("TRANSACTIONID", transaction.getTransactionDetails().get("TRANSACTIONID"));
transaction.getTransactionDetails().put("TRNAPPROVED", re.getReason());
transaction.getTransactionDetails().put("TRNORDERNUMBER", re.getId());
transaction.getTransactionDetails().put("MESSAGETEXT", null);
return transaction;
} catch(Exception e) {
throw buildException(e);
}
}
private IntegrationException buildException(Exception ex) {
if(ex instanceof CardException) {
CardException e = (CardException)ex;
// Since it's a decline, CardException will be caught
//System.out.println("Status is: " + e.getCode());
//System.out.println("Message is: " + e.getMessage());
/**
*
invalid_number The card number is not a valid credit card number.
invalid_expiry_month The card's expiration month is invalid.
invalid_expiry_year The card's expiration year is invalid.
invalid_cvc The card's security code is invalid.
incorrect_number The card number is incorrect.
expired_card The card has expired.
incorrect_cvc The card's security code is incorrect.
incorrect_zip The card's zip code failed validation.
card_declined The card was declined.
missing There is no card on a customer that is being charged.
processing_error An error occurred while processing the card.
rate_limit An error occurred due to requests hitting the API too quickly. Please let us know if you're consistently running into this error.
*/
String declineCode = e.getDeclineCode();
if("card_declined".equals(declineCode)) {
IntegrationException te = new IntegrationException(
"Can't process stripe message " + e.getMessage());
te.setExceptionType(IntegrationException.EXCEPTION_PAYMENT_DECLINED);
te.setMessageCode("message.payment.declined");
te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION);
return te;
}
if("invalid_number".equals(declineCode)) {
IntegrationException te = new IntegrationException(
"Can't process stripe message " + e.getMessage());
te.setExceptionType(IntegrationException.EXCEPTION_VALIDATION);
te.setMessageCode("messages.error.creditcard.number");
te.setErrorCode(IntegrationException.EXCEPTION_VALIDATION);
return te;
}
if("invalid_expiry_month".equals(declineCode)) {
IntegrationException te = new IntegrationException(
"Can't process stripe message " + e.getMessage());
te.setExceptionType(IntegrationException.EXCEPTION_VALIDATION);
te.setMessageCode("messages.error.creditcard.dateformat");
te.setErrorCode(IntegrationException.EXCEPTION_VALIDATION);
return te;
}
if("invalid_expiry_year".equals(declineCode)) {
IntegrationException te = new IntegrationException(
"Can't process stripe message " + e.getMessage());
te.setExceptionType(IntegrationException.EXCEPTION_VALIDATION);
te.setMessageCode("messages.error.creditcard.dateformat");
te.setErrorCode(IntegrationException.EXCEPTION_VALIDATION);
return te;
}
if("invalid_cvc".equals(declineCode)) {
IntegrationException te = new IntegrationException(
"Can't process stripe message " + e.getMessage());
te.setExceptionType(IntegrationException.EXCEPTION_VALIDATION);
te.setMessageCode("messages.error.creditcard.cvc");
te.setErrorCode(IntegrationException.EXCEPTION_VALIDATION);
return te;
}
if("incorrect_number".equals(declineCode)) {
IntegrationException te = new IntegrationException(
"Can't process stripe message " + e.getMessage());
te.setExceptionType(IntegrationException.EXCEPTION_VALIDATION);
te.setMessageCode("messages.error.creditcard.number");
te.setErrorCode(IntegrationException.EXCEPTION_VALIDATION);
return te;
}
if("incorrect_cvc".equals(declineCode)) {
IntegrationException te = new IntegrationException(
"Can't process stripe message " + e.getMessage());
te.setExceptionType(IntegrationException.EXCEPTION_VALIDATION);
te.setMessageCode("messages.error.creditcard.cvc");
te.setErrorCode(IntegrationException.EXCEPTION_VALIDATION);
return te;
}
} else if (ex instanceof InvalidRequestException) {
LOGGER.error("InvalidRequest error with stripe", ex.getMessage());
InvalidRequestException e =(InvalidRequestException)ex;
IntegrationException te = new IntegrationException(
"Can't process Stripe, missing invalid payment parameters");
te.setExceptionType(IntegrationException.TRANSACTION_EXCEPTION);
te.setMessageCode("message.payment.error");
te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION);
return te;
} else if (ex instanceof AuthenticationException) {
LOGGER.error("Authentication error with stripe", ex.getMessage());
AuthenticationException e = (AuthenticationException)ex;
// Authentication with Stripe's API failed
// (maybe you changed API keys recently)
IntegrationException te = new IntegrationException(
"Can't process Stripe, missing invalid payment parameters");
te.setExceptionType(IntegrationException.TRANSACTION_EXCEPTION);
te.setMessageCode("message.payment.error");
te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION);
return te;
} else if (ex instanceof APIConnectionException) {
LOGGER.error("API connection error with stripe", ex.getMessage());
APIConnectionException e = (APIConnectionException)ex;
// Network communication with Stripe failed
IntegrationException te = new IntegrationException(
"Can't process Stripe, missing invalid payment parameters");
te.setExceptionType(IntegrationException.TRANSACTION_EXCEPTION);
te.setMessageCode("message.payment.error");
te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION);
return te;
} else if (ex instanceof StripeException) {
LOGGER.error("Error with stripe", ex.getMessage());
StripeException e = (StripeException)ex;
// Display a very generic error to the user, and maybe send
// yourself an email
IntegrationException te = new IntegrationException(
"Can't process Stripe authorize, missing invalid payment parameters");
te.setExceptionType(IntegrationException.TRANSACTION_EXCEPTION);
te.setMessageCode("message.payment.error");
te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION);
return te;
} else if (ex instanceof Exception) {
LOGGER.error("Stripe module error", ex.getMessage());
if(ex instanceof IntegrationException) {
return (IntegrationException)ex;
} else {
IntegrationException te = new IntegrationException(
"Can't process Stripe authorize, exception", ex);
te.setExceptionType(IntegrationException.TRANSACTION_EXCEPTION);
te.setMessageCode("message.payment.error");
te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION);
return te;
}
} else {
LOGGER.error("Stripe module error", ex.getMessage());
IntegrationException te = new IntegrationException(
"Can't process Stripe authorize, exception", ex);
te.setExceptionType(IntegrationException.TRANSACTION_EXCEPTION);
te.setMessageCode("message.payment.error");
te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION);
return te;
}
return null;
}
}