Search in sources :

Example 1 with PluginProperty

use of org.killbill.billing.payment.api.PluginProperty in project killbill by killbill.

the class InvoicePaymentControlPluginApi method getPluginRefundResult.

private PriorPaymentControlResult getPluginRefundResult(final PaymentControlContext paymentControlPluginContext, final Iterable<PluginProperty> pluginProperties, final InternalCallContext internalContext) throws PaymentControlApiException {
    final Map<UUID, BigDecimal> idWithAmount = extractIdsWithAmountFromProperties(pluginProperties);
    if ((paymentControlPluginContext.getAmount() == null || paymentControlPluginContext.getAmount().compareTo(BigDecimal.ZERO) == 0) && idWithAmount.size() == 0) {
        throw new PaymentControlApiException("Abort refund call: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, String.format("Refund for payment, key = %s, aborted: requested refund amount is = %s", paymentControlPluginContext.getPaymentExternalKey(), paymentControlPluginContext.getAmount())));
    }
    final PaymentModelDao payment = paymentDao.getPayment(paymentControlPluginContext.getPaymentId(), internalContext);
    if (payment == null) {
        throw new PaymentControlApiException("Unexpected null payment");
    }
    // This will calculate the upper bound on the refund amount based on the invoice items associated with that payment.
    // Note that we are not checking that other (partial) refund occurred, but if the refund ends up being greater than what is allowed
    // the call to the gateway would fail; it would need noce to validate on our side though...
    final BigDecimal amountToBeRefunded = computeRefundAmount(payment.getId(), paymentControlPluginContext.getAmount(), idWithAmount, internalContext);
    final boolean isAborted = amountToBeRefunded.compareTo(BigDecimal.ZERO) == 0;
    if (paymentControlPluginContext.isApiPayment() && isAborted) {
        throw new PaymentControlApiException("Abort refund call: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, String.format("Refund for payment %s aborted : invoice item sum amount is %s, requested refund amount is = %s", payment.getId(), amountToBeRefunded, paymentControlPluginContext.getAmount())));
    }
    final PluginProperty prop = getPluginProperty(pluginProperties, PROP_IPCD_REFUND_WITH_ADJUSTMENTS);
    final boolean isAdjusted = prop != null ? Boolean.valueOf((String) prop.getValue()) : false;
    if (isAdjusted) {
        try {
            invoiceApi.validateInvoiceItemAdjustments(paymentControlPluginContext.getPaymentId(), idWithAmount, internalContext);
        } catch (InvoiceApiException e) {
            throw new PaymentControlApiException(String.format("Refund for payment %s aborted", payment.getId()), new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getMessage()));
        }
    }
    return new DefaultPriorPaymentControlResult(isAborted, amountToBeRefunded);
}
Also used : PluginProperty(org.killbill.billing.payment.api.PluginProperty) InvoiceApiException(org.killbill.billing.invoice.api.InvoiceApiException) PaymentModelDao(org.killbill.billing.payment.dao.PaymentModelDao) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) UUID(java.util.UUID) BigDecimal(java.math.BigDecimal) PaymentControlApiException(org.killbill.billing.control.plugin.api.PaymentControlApiException) DefaultPriorPaymentControlResult(org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult)

Example 2 with PluginProperty

use of org.killbill.billing.payment.api.PluginProperty in project killbill by killbill.

the class ControlPluginRunner method executePluginOnFailureCalls.

public OnFailurePaymentControlResult executePluginOnFailureCalls(final Account account, final UUID paymentMethodId, final UUID paymentAttemptId, final UUID paymentId, final String paymentExternalKey, final UUID transactionId, final String paymentTransactionExternalKey, final PaymentApiType paymentApiType, final TransactionType transactionType, final HPPType hppType, final BigDecimal amount, final Currency currency, final BigDecimal processedAmount, final Currency processedCurrency, final boolean isApiPayment, final List<String> paymentControlPluginNames, final Iterable<PluginProperty> pluginProperties, final CallContext callContext) {
    final PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account, paymentMethodId, paymentAttemptId, paymentId, paymentExternalKey, transactionId, paymentTransactionExternalKey, paymentApiType, transactionType, hppType, amount, currency, processedAmount, processedCurrency, isApiPayment, callContext);
    DateTime candidate = null;
    Iterable<PluginProperty> inputPluginProperties = pluginProperties;
    for (final String pluginName : paymentControlPluginNames) {
        final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
        if (plugin != null) {
            try {
                log.debug("Calling onSuccessCall of plugin {}", pluginName);
                final OnFailurePaymentControlResult result = plugin.onFailureCall(inputPaymentControlContext, inputPluginProperties);
                log.debug("Successful executed onSuccessCall of plugin {}", pluginName);
                if (result == null) {
                    // Nothing returned by the plugin
                    continue;
                }
                if (candidate == null) {
                    candidate = result.getNextRetryDate();
                } else if (result.getNextRetryDate() != null) {
                    candidate = candidate.compareTo(result.getNextRetryDate()) > 0 ? result.getNextRetryDate() : candidate;
                }
                if (result.getAdjustedPluginProperties() != null) {
                    inputPluginProperties = result.getAdjustedPluginProperties();
                }
            } catch (final PaymentControlApiException e) {
                log.warn("Error during onFailureCall for plugin='{}', paymentExternalKey='{}'", pluginName, inputPaymentControlContext.getPaymentExternalKey(), e);
                return new DefaultFailureCallResult(candidate, inputPluginProperties);
            }
        }
    }
    return new DefaultFailureCallResult(candidate, inputPluginProperties);
}
Also used : DefaultFailureCallResult(org.killbill.billing.payment.retry.DefaultFailureCallResult) PluginProperty(org.killbill.billing.payment.api.PluginProperty) OnFailurePaymentControlResult(org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult) PaymentControlPluginApi(org.killbill.billing.control.plugin.api.PaymentControlPluginApi) PaymentControlContext(org.killbill.billing.control.plugin.api.PaymentControlContext) DateTime(org.joda.time.DateTime) PaymentControlApiException(org.killbill.billing.control.plugin.api.PaymentControlApiException)

Example 3 with PluginProperty

use of org.killbill.billing.payment.api.PluginProperty in project killbill by killbill.

the class ControlPluginRunner method executePluginPriorCalls.

public PriorPaymentControlResult executePluginPriorCalls(final Account account, final UUID paymentMethodId, final UUID paymentAttemptId, final UUID paymentId, final String paymentExternalKey, final UUID paymentTransactionId, final String paymentTransactionExternalKey, final PaymentApiType paymentApiType, final TransactionType transactionType, final HPPType hppType, final BigDecimal amount, final Currency currency, final BigDecimal processedAmount, final Currency processedCurrency, final boolean isApiPayment, final List<String> paymentControlPluginNames, final Iterable<PluginProperty> pluginProperties, final CallContext callContext) throws PaymentControlApiException {
    // Return as soon as the first plugin aborts, or the last result for the last plugin
    PriorPaymentControlResult prevResult = new DefaultPriorPaymentControlResult(false, amount, currency, paymentMethodId, pluginProperties);
    // Those values are adjusted prior each call with the result of what previous call to plugin returned
    UUID inputPaymentMethodId = paymentMethodId;
    BigDecimal inputAmount = amount;
    Currency inputCurrency = currency;
    Iterable<PluginProperty> inputPluginProperties = pluginProperties;
    PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account, paymentMethodId, paymentAttemptId, paymentId, paymentExternalKey, paymentTransactionId, paymentTransactionExternalKey, paymentApiType, transactionType, hppType, amount, currency, processedAmount, processedCurrency, isApiPayment, callContext);
    for (final String pluginName : paymentControlPluginNames) {
        final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
        if (plugin == null) {
            // First call to plugin, we log warn, if plugin is not registered
            log.warn("Skipping unknown payment control plugin {} when fetching results", pluginName);
            continue;
        }
        log.debug("Calling priorCall of plugin {}", pluginName);
        prevResult = plugin.priorCall(inputPaymentControlContext, inputPluginProperties);
        log.debug("Successful executed priorCall of plugin {}", pluginName);
        if (prevResult == null) {
            // Nothing returned by the plugin
            continue;
        }
        if (prevResult.getAdjustedPaymentMethodId() != null) {
            inputPaymentMethodId = prevResult.getAdjustedPaymentMethodId();
        }
        if (prevResult.getAdjustedAmount() != null) {
            inputAmount = prevResult.getAdjustedAmount();
        }
        if (prevResult.getAdjustedCurrency() != null) {
            inputCurrency = prevResult.getAdjustedCurrency();
        }
        if (prevResult.getAdjustedPluginProperties() != null) {
            inputPluginProperties = prevResult.getAdjustedPluginProperties();
        }
        if (prevResult.isAborted()) {
            throw new PaymentControlApiAbortException(pluginName);
        }
        inputPaymentControlContext = new DefaultPaymentControlContext(account, inputPaymentMethodId, paymentAttemptId, paymentId, paymentExternalKey, paymentTransactionId, paymentTransactionExternalKey, paymentApiType, transactionType, hppType, inputAmount, inputCurrency, processedAmount, processedCurrency, isApiPayment, callContext);
    }
    // Rebuild latest result to include inputPluginProperties
    prevResult = new DefaultPriorPaymentControlResult(prevResult != null && prevResult.isAborted(), inputPaymentMethodId, inputAmount, inputCurrency, inputPluginProperties);
    return prevResult;
}
Also used : PluginProperty(org.killbill.billing.payment.api.PluginProperty) PaymentControlPluginApi(org.killbill.billing.control.plugin.api.PaymentControlPluginApi) Currency(org.killbill.billing.catalog.api.Currency) DefaultPriorPaymentControlResult(org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult) PriorPaymentControlResult(org.killbill.billing.control.plugin.api.PriorPaymentControlResult) PaymentControlContext(org.killbill.billing.control.plugin.api.PaymentControlContext) UUID(java.util.UUID) DefaultPriorPaymentControlResult(org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult) BigDecimal(java.math.BigDecimal)

Example 4 with PluginProperty

use of org.killbill.billing.payment.api.PluginProperty in project killbill by killbill.

the class PluginControlPaymentProcessor method retryPaymentTransaction.

public void retryPaymentTransaction(final UUID attemptId, final List<String> paymentControlPluginNames, final InternalCallContext internalCallContext) {
    final PaymentAttemptModelDao attempt = paymentDao.getPaymentAttempt(attemptId, internalCallContext);
    log.info("Retrying attemptId='{}', paymentExternalKey='{}', transactionExternalKey='{}'. paymentControlPluginNames='{}'", attemptId, attempt.getPaymentExternalKey(), attempt.getTransactionExternalKey(), paymentControlPluginNames);
    final PaymentModelDao paymentModelDao = paymentDao.getPaymentByExternalKey(attempt.getPaymentExternalKey(), internalCallContext);
    final UUID paymentId = paymentModelDao != null ? paymentModelDao.getId() : null;
    final CallContext callContext = buildCallContext(internalCallContext);
    final String transactionType = TransactionType.PURCHASE.name();
    Account account = null;
    Payment payment = null;
    PaymentTransaction paymentTransaction = null;
    try {
        account = accountInternalApi.getAccountById(attempt.getAccountId(), internalCallContext);
        final State state = paymentControlStateMachineHelper.getState(attempt.getStateName());
        final Iterable<PluginProperty> pluginProperties = PluginPropertySerializer.deserialize(attempt.getPluginProperties());
        logEnterAPICall(log, transactionType, account, attempt.getPaymentMethodId(), paymentId, null, attempt.getAmount(), attempt.getCurrency(), attempt.getPaymentExternalKey(), attempt.getTransactionExternalKey(), null, paymentControlPluginNames);
        payment = pluginControlledPaymentAutomatonRunner.run(state, false, attempt.getTransactionType(), ControlOperation.valueOf(attempt.getTransactionType().toString()), account, attempt.getPaymentMethodId(), paymentId, attempt.getPaymentExternalKey(), attempt.getTransactionExternalKey(), attempt.getAmount(), attempt.getCurrency(), pluginProperties, paymentControlPluginNames, callContext, internalCallContext);
        paymentTransaction = Iterables.<PaymentTransaction>find(Lists.<PaymentTransaction>reverse(payment.getTransactions()), new Predicate<PaymentTransaction>() {

            @Override
            public boolean apply(final PaymentTransaction input) {
                return attempt.getTransactionExternalKey().equals(input.getExternalKey());
            }
        });
    } catch (final AccountApiException e) {
        log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
    } catch (final PaymentApiException e) {
        // Log exception unless nothing left to be paid
        if (e.getCode() == ErrorCode.PAYMENT_PLUGIN_API_ABORTED.getCode() && paymentControlPluginNames != null && paymentControlPluginNames.size() == 1 && InvoicePaymentControlPluginApi.PLUGIN_NAME.equals(paymentControlPluginNames.get(0))) {
            log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'. Invoice has already been paid", attemptId, toPluginNamesOnError(paymentControlPluginNames));
        } else {
            log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
        }
    } catch (final PluginPropertySerializerException e) {
        log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
    } catch (final MissingEntryException e) {
        log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
    } finally {
        logExitAPICall(log, transactionType, account, payment != null ? payment.getPaymentMethodId() : null, payment != null ? payment.getId() : null, paymentTransaction != null ? paymentTransaction.getId() : null, paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null, paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null, payment != null ? payment.getExternalKey() : null, paymentTransaction != null ? paymentTransaction.getExternalKey() : null, paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null, paymentControlPluginNames, null);
    }
}
Also used : PaymentAttemptModelDao(org.killbill.billing.payment.dao.PaymentAttemptModelDao) Account(org.killbill.billing.account.api.Account) PaymentModelDao(org.killbill.billing.payment.dao.PaymentModelDao) PluginPropertySerializerException(org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) CallContext(org.killbill.billing.util.callcontext.CallContext) Predicate(com.google.common.base.Predicate) PaymentTransaction(org.killbill.billing.payment.api.PaymentTransaction) PluginProperty(org.killbill.billing.payment.api.PluginProperty) Payment(org.killbill.billing.payment.api.Payment) State(org.killbill.automaton.State) AccountApiException(org.killbill.billing.account.api.AccountApiException) MissingEntryException(org.killbill.automaton.MissingEntryException) UUID(java.util.UUID)

Example 5 with PluginProperty

use of org.killbill.billing.payment.api.PluginProperty in project killbill by killbill.

the class PluginControlPaymentProcessor method notifyPendingPaymentOfStateChanged.

public Payment notifyPendingPaymentOfStateChanged(final boolean isApiPayment, final Account account, final UUID paymentTransactionId, final boolean isSuccess, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
    final PaymentTransactionModelDao paymentTransactionModelDao = paymentDao.getPaymentTransaction(paymentTransactionId, internalCallContext);
    final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionModelDao.getTransactionExternalKey(), internalCallContext);
    final PaymentAttemptModelDao attempt = Iterables.find(attempts, new Predicate<PaymentAttemptModelDao>() {

        @Override
        public boolean apply(final PaymentAttemptModelDao input) {
            return input.getTransactionId().equals(paymentTransactionId);
        }
    });
    final Iterable<PluginProperty> pluginProperties;
    try {
        pluginProperties = PluginPropertySerializer.deserialize(attempt.getPluginProperties());
    } catch (final PluginPropertySerializerException e) {
        throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, String.format("Unable to deserialize payment attemptId='%s' properties", attempt.getId()));
    }
    return pluginControlledPaymentAutomatonRunner.run(isApiPayment, isSuccess, paymentTransactionModelDao.getTransactionType(), ControlOperation.NOTIFICATION_OF_STATE_CHANGE, account, attempt.getPaymentMethodId(), paymentTransactionModelDao.getPaymentId(), attempt.getPaymentExternalKey(), paymentTransactionId, paymentTransactionModelDao.getTransactionExternalKey(), paymentTransactionModelDao.getAmount(), paymentTransactionModelDao.getCurrency(), pluginProperties, paymentControlPluginNames, callContext, internalCallContext);
}
Also used : PaymentAttemptModelDao(org.killbill.billing.payment.dao.PaymentAttemptModelDao) PluginProperty(org.killbill.billing.payment.api.PluginProperty) PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) PluginPropertySerializerException(org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException)

Aggregations

PluginProperty (org.killbill.billing.payment.api.PluginProperty)105 UUID (java.util.UUID)45 Account (org.killbill.billing.account.api.Account)42 ApiOperation (io.swagger.annotations.ApiOperation)35 ApiResponses (io.swagger.annotations.ApiResponses)35 Produces (javax.ws.rs.Produces)35 TimedResource (org.killbill.commons.metrics.TimedResource)35 Test (org.testng.annotations.Test)33 Path (javax.ws.rs.Path)32 Payment (org.killbill.billing.payment.api.Payment)30 CallContext (org.killbill.billing.util.callcontext.CallContext)29 ArrayList (java.util.ArrayList)19 LocalDate (org.joda.time.LocalDate)19 Consumes (javax.ws.rs.Consumes)17 BigDecimal (java.math.BigDecimal)16 TenantContext (org.killbill.billing.util.callcontext.TenantContext)15 GET (javax.ws.rs.GET)14 POST (javax.ws.rs.POST)13 DefaultEntitlement (org.killbill.billing.entitlement.api.DefaultEntitlement)13 AccountAuditLogs (org.killbill.billing.util.audit.AccountAuditLogs)13