Skip to content

Commit

Permalink
Merge branch 'master' of github.com:killbill/killbill
Browse files Browse the repository at this point in the history
  • Loading branch information
sbrossie committed Jan 23, 2017
2 parents c8d02b0 + 4ad2d39 commit 00e81cf
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 29 deletions.
Expand Up @@ -87,9 +87,11 @@
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
Expand Down Expand Up @@ -241,17 +243,45 @@ public Payment getPaymentByExternalKey(final String paymentExternalKey, final bo

public Pagination<Payment> getPayments(final Long offset, final Long limit, final boolean withPluginInfo, final boolean withAttempts,
final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) {
return getEntityPaginationFromPlugins(true,
getAvailablePlugins(),
offset,
limit,
new EntityPaginationBuilder<Payment, PaymentApiException>() {
@Override
public Pagination<Payment> build(final Long offset, final Long limit, final String pluginName) throws PaymentApiException {
return getPayments(offset, limit, pluginName, withPluginInfo, withAttempts, properties, tenantContext, internalTenantContext);
}
}
);
final Map<UUID, Optional<PaymentPluginApi>> paymentMethodIdToPaymentPluginApi = new HashMap<UUID, Optional<PaymentPluginApi>>();

try {
return getEntityPagination(limit,
new SourcePaginationBuilder<PaymentModelDao, PaymentApiException>() {
@Override
public Pagination<PaymentModelDao> build() {
// Find all payments for all accounts
return paymentDao.get(offset, limit, internalTenantContext);
}
},
new Function<PaymentModelDao, Payment>() {
@Override
public Payment apply(final PaymentModelDao paymentModelDao) {
final PaymentPluginApi pluginApi;
if (!withPluginInfo) {
pluginApi = null;
} else {
if (paymentMethodIdToPaymentPluginApi.get(paymentModelDao.getPaymentMethodId()) == null) {
try {
final PaymentPluginApi paymentProviderPlugin = getPaymentProviderPlugin(paymentModelDao.getPaymentMethodId(), internalTenantContext);
paymentMethodIdToPaymentPluginApi.put(paymentModelDao.getPaymentMethodId(), Optional.<PaymentPluginApi>of(paymentProviderPlugin));
} catch (final PaymentApiException e) {
log.warn("Unable to retrieve PaymentPluginApi for paymentMethodId='{}'", paymentModelDao.getPaymentMethodId(), e);
// We use Optional to avoid printing the log line for each result
paymentMethodIdToPaymentPluginApi.put(paymentModelDao.getPaymentMethodId(), Optional.<PaymentPluginApi>absent());
}
}
pluginApi = paymentMethodIdToPaymentPluginApi.get(paymentModelDao.getPaymentMethodId()).orNull();
}
final List<PaymentTransactionInfoPlugin> pluginInfo = getPaymentTransactionInfoPluginsIfNeeded(pluginApi, paymentModelDao, tenantContext);
return toPayment(paymentModelDao.getId(), pluginInfo, withAttempts, internalTenantContext);
}
}
);
} catch (final PaymentApiException e) {
log.warn("Unable to get payments", e);
return new DefaultPagination<Payment>(offset, limit, null, null, ImmutableSet.<Payment>of().iterator());
}
}

public Pagination<Payment> getPayments(final Long offset, final Long limit, final String pluginName, final boolean withPluginInfo, final boolean withAttempts, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
Expand Down
Expand Up @@ -78,6 +78,35 @@ public class PaymentStateMachineHelper {

private final StateMachineConfigCache stateMachineConfigCache;

public static final String[] STATE_NAMES = {AUTH_ERRORED,
AUTHORIZE_FAILED,
AUTHORIZE_PENDING,
AUTHORIZE_SUCCESS,
CAPTURE_ERRORED,
CAPTURE_FAILED,
CAPTURE_PENDING,
CAPTURE_SUCCESS,
CHARGEBACK_ERRORED,
CHARGEBACK_FAILED,
CHARGEBACK_PENDING,
CHARGEBACK_SUCCESS,
CREDIT_ERRORED,
CREDIT_FAILED,
CREDIT_PENDING,
CREDIT_SUCCESS,
PURCHASE_ERRORED,
PURCHASE_FAILED,
PURCHASE_PENDING,
PURCHASE_SUCCESS,
REFUND_ERRORED,
REFUND_FAILED,
REFUND_PENDING,
REFUND_SUCCESS,
VOID_ERRORED,
VOID_FAILED,
VOID_PENDING,
VOID_SUCCESS};

@Inject
public PaymentStateMachineHelper(final StateMachineConfigCache stateMachineConfigCache) {
this.stateMachineConfigCache = stateMachineConfigCache;
Expand Down
Expand Up @@ -19,16 +19,19 @@
package org.killbill.billing.payment.dao;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;

import javax.annotation.Nullable;
import javax.inject.Inject;

import org.joda.time.DateTime;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Currency;
Expand All @@ -38,17 +41,20 @@
import org.killbill.billing.payment.api.DefaultPaymentInfoEvent;
import org.killbill.billing.payment.api.DefaultPaymentPluginErrorEvent;
import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.PaymentMethod;
import org.killbill.billing.payment.api.PaymentTransaction;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.dao.NonEntityDao;
import org.killbill.billing.util.entity.Entity;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper;
import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
import org.killbill.billing.util.entity.dao.EntityDaoBase;
import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
Expand All @@ -66,19 +72,18 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;

public class DefaultPaymentDao implements PaymentDao {
public class DefaultPaymentDao extends EntityDaoBase<PaymentModelDao, Payment, PaymentApiException> implements PaymentDao {

private static final Logger log = LoggerFactory.getLogger(DefaultPaymentDao.class);

private final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
private final DefaultPaginationSqlDaoHelper paginationHelper;
private final PersistentBus eventBus;
private final Clock clock;

@Inject
public DefaultPaymentDao(final IDBI dbi, final Clock clock, final CacheControllerDispatcher cacheControllerDispatcher,
final NonEntityDao nonEntityDao, final InternalCallContextFactory internalCallContextFactory, final PersistentBus eventBus) {
this.transactionalSqlDao = new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao, internalCallContextFactory);
super(new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao, internalCallContextFactory), PaymentSqlDao.class);
this.paginationHelper = new DefaultPaginationSqlDaoHelper(transactionalSqlDao);
this.eventBus = eventBus;
this.clock = clock;
Expand Down Expand Up @@ -248,23 +253,41 @@ public Iterator<PaymentModelDao> build(final PaymentSqlDao paymentSqlDao, final

@Override
public Pagination<PaymentModelDao> searchPayments(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
// Optimization: if the search key looks like a state name (e.g. _ERRORED), assume the user is searching by state only
final List<String> paymentStates = shouldSearchByStateNameOnly(searchKey);

final String likeSearchKey = String.format("%%%s%%", searchKey);
return paginationHelper.getPagination(PaymentSqlDao.class,
new PaginationIteratorBuilder<PaymentModelDao, Payment, PaymentSqlDao>() {
@Override
public Long getCount(final PaymentSqlDao paymentSqlDao, final InternalTenantContext context) {
return paymentSqlDao.getSearchCount(searchKey, String.format("%%%s%%", searchKey), context);
return !paymentStates.isEmpty() ? paymentSqlDao.getSearchByStateCount(paymentStates, context) : paymentSqlDao.getSearchCount(searchKey, likeSearchKey, context);
}

@Override
public Iterator<PaymentModelDao> build(final PaymentSqlDao paymentSqlDao, final Long limit, final InternalTenantContext context) {
return paymentSqlDao.search(searchKey, String.format("%%%s%%", searchKey), offset, limit, context);
return !paymentStates.isEmpty() ? paymentSqlDao.searchByState(paymentStates, offset, limit, context) : paymentSqlDao.search(searchKey, likeSearchKey, offset, limit, context);
}
},
offset,
limit,
context);
}

private List<String> shouldSearchByStateNameOnly(final String searchKey) {
final Pattern pattern = Pattern.compile(".*" + searchKey + ".*");

// Note that technically, we should look at all of the available state names in the database instead since the state machine is configurable. The common use-case
// is to override transitions though, not to introduce new states, and since some of it is already hardcoded in PaymentStateMachineHelper anyways, it's probably good enough for now.
final List<String> stateNames = new ArrayList<String>();
for (final String stateName : PaymentStateMachineHelper.STATE_NAMES) {
if (pattern.matcher(stateName).matches()) {
stateNames.add(stateName);
}
}
return stateNames;
}

@Override
public PaymentModelDao insertPaymentWithFirstTransaction(final PaymentModelDao payment, final PaymentTransactionModelDao paymentTransaction, final InternalCallContext context) {

Expand Down Expand Up @@ -650,4 +673,9 @@ private void postPaymentEventFromTransaction(final UUID accountId,
private InternalCallContext contextWithUpdatedDate(final InternalCallContext input) {
return new InternalCallContext(input, clock.getUTCNow());
}

@Override
protected PaymentApiException generateAlreadyExistsException(final PaymentModelDao entity, final InternalCallContext context) {
return new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, "Payment already exists");
}
}
Expand Up @@ -24,11 +24,14 @@
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.entity.dao.EntityDao;

public interface PaymentDao {
public interface PaymentDao extends EntityDao<PaymentModelDao, Payment, PaymentApiException> {

public Pagination<PaymentTransactionModelDao> getByTransactionStatusAcrossTenants(final Iterable<TransactionStatus> transactionStatuses, DateTime createdBeforeDate, DateTime createdAfterDate, final Long offset, final Long limit);

Expand Down
Expand Up @@ -33,7 +33,6 @@
import org.skife.jdbi.v2.sqlobject.BindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.customizers.Define;

@EntitySqlDaoStringTemplate
public interface PaymentSqlDao extends EntitySqlDao<PaymentModelDao, Payment> {
Expand Down Expand Up @@ -67,6 +66,17 @@ public List<PaymentModelDao> getPaymentsByStatesAcrossTenants(@StateCollectionBi
@Bind("createdAfterDate") final Date createdAfterDate,
@Bind("limit") final int limit);

@SqlQuery
@SmartFetchSize(shouldStream = true)
public Iterator<PaymentModelDao> searchByState(@PaymentStateCollectionBinder final Collection<String> paymentStates,
@Bind("offset") final Long offset,
@Bind("rowCount") final Long rowCount,
@BindBean final InternalTenantContext context);

@SqlQuery
public Long getSearchByStateCount(@PaymentStateCollectionBinder final Collection<String> paymentStates,
@BindBean final InternalTenantContext context);

@SqlQuery
@SmartFetchSize(shouldStream = true)
public Iterator<PaymentModelDao> getByPluginName(@Bind("pluginName") final String pluginName,
Expand Down
@@ -0,0 +1,57 @@
/*
* Copyright 2014-2017 Groupon, Inc
* Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package org.killbill.billing.payment.dao;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collection;

import org.killbill.billing.payment.dao.PaymentStateCollectionBinder.PaymentStateCollectionBinderFactory;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.sqlobject.Binder;
import org.skife.jdbi.v2.sqlobject.BinderFactory;
import org.skife.jdbi.v2.sqlobject.BindingAnnotation;

@BindingAnnotation(PaymentStateCollectionBinderFactory.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface PaymentStateCollectionBinder {

public static class PaymentStateCollectionBinderFactory implements BinderFactory {

@Override
public Binder build(final Annotation annotation) {
return new Binder<PaymentStateCollectionBinder, Collection<String>>() {

@Override
public void bind(final SQLStatement<?> query, final PaymentStateCollectionBinder bind, final Collection<String> allPaymentState) {
query.define("states", allPaymentState);

int idx = 0;
for (final String paymentState : allPaymentState) {
query.bind("state_" + idx, paymentState);
idx++;
}
}
};
}
}
}
Expand Up @@ -80,7 +80,33 @@ searchQuery(prefix) ::= <<
or <prefix>account_id = :searchKey
or <prefix>payment_method_id = :searchKey
or <prefix>external_key like :likeSearchKey
or <prefix>state_name like :likeSearchKey
>>

searchByState(states) ::= <<
select
<allTableFields("t.")>
from <tableName()> t
join (
select <recordIdField()>
from <tableName()>
where state_name in (<states: {state | :state_<i0>}; separator="," >)
<AND_CHECK_TENANT()>
<andCheckSoftDeletionWithComma()>
order by <recordIdField()>
limit :rowCount offset :offset
) optimization on <recordIdField("optimization.")> = <recordIdField("t.")>
order by <recordIdField("t.")>
;
>>

getSearchByStateCount(states) ::= <<
select
count(1) as count
from <tableName()> t
where t.state_name in (<states: {state | :state_<i0>}; separator="," >)
<andCheckSoftDeletionWithComma("t.")>
<AND_CHECK_TENANT("t.")>
;
>>

getByPluginName() ::= <<
Expand Down

0 comments on commit 00e81cf

Please sign in to comment.