Search in sources :

Example 31 with PaymentApiException

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

the class PaymentProcessor method performOperation.

private Payment performOperation(final boolean isApiPayment, final boolean runJanitor, @Nullable final UUID attemptId, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, @Nullable final UUID transactionId, @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey, @Nullable final UUID paymentIdForNewPayment, @Nullable final UUID paymentTransactionIdForNewPaymentTransaction, final boolean shouldLockAccountAndDispatch, @Nullable final OperationResult overridePluginOperationResult, final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
    final PaymentStateContext paymentStateContext = paymentAutomatonRunner.buildPaymentStateContext(isApiPayment, transactionType, account, attemptId, paymentMethodId != null ? paymentMethodId : account.getPaymentMethodId(), paymentId, transactionId, paymentExternalKey, paymentTransactionExternalKey, amount, currency, paymentIdForNewPayment, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, overridePluginOperationResult, properties, callContext, internalCallContext);
    final PaymentAutomatonDAOHelper daoHelper = paymentAutomatonRunner.buildDaoHelper(paymentStateContext, internalCallContext);
    String currentStateName = null;
    if (paymentStateContext.getPaymentId() != null) {
        PaymentModelDao paymentModelDao = daoHelper.getPayment();
        // Sanity: verify the payment belongs to the right account (in case it was looked-up by payment or transaction external key)
        if (!paymentModelDao.getAccountRecordId().equals(internalCallContext.getAccountRecordId())) {
            throw new PaymentApiException(ErrorCode.PAYMENT_DIFFERENT_ACCOUNT_ID, paymentStateContext.getPaymentId());
        }
        // Note: the list needs to be modifiable for invokeJanitor
        final Collection<PaymentTransactionModelDao> paymentTransactionsForCurrentPayment = new LinkedList<PaymentTransactionModelDao>(daoHelper.getPaymentDao().getTransactionsForPayment(paymentStateContext.getPaymentId(), paymentStateContext.getInternalCallContext()));
        // prevent disallowed transitions in case the state couldn't be fixed (or if it's already in a final state).
        if (runJanitor) {
            final PaymentPluginApi plugin = getPaymentProviderPlugin(paymentModelDao.getPaymentMethodId(), true, internalCallContext);
            final List<PaymentTransactionInfoPlugin> pluginTransactions = getPaymentTransactionInfoPlugins(plugin, paymentModelDao, properties, callContext);
            paymentModelDao = invokeJanitor(paymentModelDao, paymentTransactionsForCurrentPayment, pluginTransactions, internalCallContext);
        }
        if (paymentStateContext.getPaymentTransactionExternalKey() != null) {
            final List<PaymentTransactionModelDao> allPaymentTransactionsForKey = daoHelper.getPaymentDao().getPaymentTransactionsByExternalKey(paymentStateContext.getPaymentTransactionExternalKey(), internalCallContext);
            runSanityOnTransactionExternalKey(allPaymentTransactionsForKey, paymentStateContext, internalCallContext);
        }
        if (paymentStateContext.getTransactionId() != null || paymentStateContext.getPaymentTransactionExternalKey() != null) {
            // If a transaction id or key is passed, we are maybe completing an existing transaction (unless a new key was provided)
            PaymentTransactionModelDao transactionToComplete = findTransactionToCompleteAndRunSanityChecks(paymentModelDao, paymentTransactionsForCurrentPayment, paymentStateContext, internalCallContext);
            if (transactionToComplete != null) {
                final UUID transactionToCompleteId = transactionToComplete.getId();
                transactionToComplete = Iterables.<PaymentTransactionModelDao>find(paymentTransactionsForCurrentPayment, new Predicate<PaymentTransactionModelDao>() {

                    @Override
                    public boolean apply(final PaymentTransactionModelDao input) {
                        return transactionToCompleteId.equals(input.getId());
                    }
                });
                // We can't tell where we should be in the state machine - bail (cannot be enforced by the state machine unfortunately because UNKNOWN and PLUGIN_FAILURE are both treated as EXCEPTION)
                if (transactionToComplete.getTransactionStatus() == TransactionStatus.UNKNOWN) {
                    throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_OPERATION, paymentStateContext.getTransactionType(), transactionToComplete.getTransactionStatus());
                }
                paymentStateContext.setPaymentTransactionModelDao(transactionToComplete);
            }
        }
        // Use the original payment method id of the payment being completed
        paymentStateContext.setPaymentMethodId(paymentModelDao.getPaymentMethodId());
        // We always take the last successful state name to permit retries on failures
        currentStateName = paymentModelDao.getLastSuccessStateName();
    }
    final UUID nonNullPaymentId = paymentAutomatonRunner.run(paymentStateContext, daoHelper, currentStateName, transactionType);
    return getPayment(nonNullPaymentId, true, false, properties, callContext, internalCallContext);
}
Also used : PaymentModelDao(org.killbill.billing.payment.dao.PaymentModelDao) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) LinkedList(java.util.LinkedList) Predicate(com.google.common.base.Predicate) PaymentPluginApi(org.killbill.billing.payment.plugin.api.PaymentPluginApi) PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) PaymentAutomatonDAOHelper(org.killbill.billing.payment.core.sm.PaymentAutomatonDAOHelper) PaymentStateContext(org.killbill.billing.payment.core.sm.PaymentStateContext) PaymentTransactionInfoPlugin(org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin) UUID(java.util.UUID)

Example 32 with PaymentApiException

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

the class EhCacheStateMachineConfigCache method getPaymentStateMachineConfig.

@Override
public StateMachineConfig getPaymentStateMachineConfig(final String pluginName, final InternalTenantContext tenantContext) throws PaymentApiException {
    if (tenantContext.getTenantRecordId() == InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID || cacheController == null) {
        return defaultPaymentStateMachineConfig;
    }
    final String pluginConfigKey = getCacheKeyName(pluginName, tenantContext);
    final CacheLoaderArgument cacheLoaderArgument = createCacheLoaderArgument(pluginName);
    try {
        StateMachineConfig pluginPaymentStateMachineConfig = (StateMachineConfig) cacheController.get(pluginConfigKey, cacheLoaderArgument);
        // It means we are using the default state machine config in a multi-tenant deployment
        if (pluginPaymentStateMachineConfig == null) {
            pluginPaymentStateMachineConfig = defaultPaymentStateMachineConfig;
            cacheController.add(pluginConfigKey, pluginPaymentStateMachineConfig);
        }
        return pluginPaymentStateMachineConfig;
    } catch (final IllegalStateException e) {
        // TODO 0.17 proper error code
        throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, "Invalid payment state machine");
    }
}
Also used : StateMachineConfig(org.killbill.automaton.StateMachineConfig) DefaultStateMachineConfig(org.killbill.automaton.DefaultStateMachineConfig) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) CacheLoaderArgument(org.killbill.billing.util.cache.CacheLoaderArgument)

Example 33 with PaymentApiException

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

the class TenantStateMachineConfigCacheLoader method load.

@Override
public Object load(final Object key, final Object argument) {
    checkCacheLoaderStatus();
    if (!(key instanceof String)) {
        throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
    }
    if (!(argument instanceof CacheLoaderArgument)) {
        throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
    }
    final String[] parts = ((String) key).split(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
    final String rawKey = parts[0];
    final Matcher matcher = PATTERN.matcher(rawKey);
    if (!matcher.matches()) {
        throw new IllegalArgumentException("Unexpected key " + rawKey);
    }
    final String pluginName = matcher.group(1);
    final String tenantRecordId = parts[1];
    final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
    final LoaderCallback callback = (LoaderCallback) cacheLoaderArgument.getArgs()[0];
    final InternalTenantContext internalTenantContext = new InternalTenantContext(Long.valueOf(tenantRecordId));
    final String stateMachineConfigXML = tenantApi.getPluginPaymentStateMachineConfig(pluginName, internalTenantContext);
    if (stateMachineConfigXML == null) {
        return null;
    }
    try {
        log.info("Loading config state machine cache for pluginName='{}', tenantRecordId='{}'", pluginName, internalTenantContext.getTenantRecordId());
        return callback.loadStateMachineConfig(stateMachineConfigXML);
    } catch (final PaymentApiException e) {
        throw new IllegalStateException(String.format("Failed to de-serialize state machine config for tenantRecordId='%s'", internalTenantContext.getTenantRecordId()), e);
    }
}
Also used : Matcher(java.util.regex.Matcher) InternalTenantContext(org.killbill.billing.callcontext.InternalTenantContext) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException)

Example 34 with PaymentApiException

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

the class JaxRsResourceBase method createPurchaseForInvoice.

protected Payment createPurchaseForInvoice(final Account account, final UUID invoiceId, final BigDecimal amountToPay, final UUID paymentMethodId, final Boolean externalPayment, final String paymentExternalKey, final String transactionExternalKey, final Iterable<PluginProperty> pluginProperties, final CallContext callContext) throws PaymentApiException {
    final List<PluginProperty> properties = new ArrayList<PluginProperty>();
    final Iterator<PluginProperty> pluginPropertyIterator = pluginProperties.iterator();
    while (pluginPropertyIterator.hasNext()) {
        properties.add(pluginPropertyIterator.next());
    }
    final PluginProperty invoiceProperty = new PluginProperty("IPCD_INVOICE_ID", /* InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID (contract with plugin)  */
    invoiceId.toString(), false);
    properties.add(invoiceProperty);
    try {
        return paymentApi.createPurchaseWithPaymentControl(account, paymentMethodId, null, amountToPay, account.getCurrency(), paymentExternalKey, transactionExternalKey, properties, createInvoicePaymentControlPluginApiPaymentOptions(externalPayment), callContext);
    } catch (final PaymentApiException e) {
        if (e.getCode() == ErrorCode.PAYMENT_PLUGIN_EXCEPTION.getCode() && e.getMessage().contains("Aborted Payment for invoice")) {
            return null;
        }
        throw e;
    }
}
Also used : PluginProperty(org.killbill.billing.payment.api.PluginProperty) ArrayList(java.util.ArrayList) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException)

Example 35 with PaymentApiException

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

the class TestInvoicePayment method testWithUNKNOWNPaymentFixedToSuccess.

@Test(groups = "slow", description = "Verify fixPaymentTransactionState behavior for UNKNOWN->SUCCESS")
public void testWithUNKNOWNPaymentFixedToSuccess() throws Exception {
    // Verify integration with Overdue in that particular test
    final String configXml = "<overdueConfig>" + "   <accountOverdueStates>" + "       <initialReevaluationInterval>" + "           <unit>DAYS</unit><number>1</number>" + "       </initialReevaluationInterval>" + "       <state name=\"OD1\">" + "           <condition>" + "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" + "                   <unit>DAYS</unit><number>1</number>" + "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" + "           </condition>" + "           <externalMessage>Reached OD1</externalMessage>" + "           <blockChanges>true</blockChanges>" + "           <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" + "       </state>" + "   </accountOverdueStates>" + "</overdueConfig>";
    final InputStream is = new ByteArrayInputStream(configXml.getBytes());
    final DefaultOverdueConfig config = XMLLoader.getObjectFromStreamNoValidation(is, DefaultOverdueConfig.class);
    overdueConfigCache.loadDefaultOverdueConfig(config);
    clock.setDay(new LocalDate(2012, 4, 1));
    final AccountData accountData = getAccountData(1);
    final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
    accountChecker.checkAccount(account.getId(), accountData, callContext);
    checkODState(OverdueWrapper.CLEAR_STATE_NAME, account.getId());
    paymentPlugin.makeNextPaymentUnknown();
    final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
    addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_PLUGIN_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
    invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 1), callContext);
    final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
    assertEquals(invoices.size(), 2);
    final Invoice invoice1 = invoices.get(0).getInvoiceItems().get(0).getInvoiceItemType() == InvoiceItemType.RECURRING ? invoices.get(0) : invoices.get(1);
    assertTrue(invoice1.getBalance().compareTo(new BigDecimal("249.95")) == 0);
    assertTrue(invoice1.getPaidAmount().compareTo(BigDecimal.ZERO) == 0);
    assertTrue(invoice1.getChargedAmount().compareTo(new BigDecimal("249.95")) == 0);
    assertEquals(invoice1.getPayments().size(), 1);
    assertEquals(invoice1.getPayments().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
    assertEquals(invoice1.getPayments().get(0).getCurrency(), Currency.USD);
    assertFalse(invoice1.getPayments().get(0).isSuccess());
    assertNotNull(invoice1.getPayments().get(0).getPaymentId());
    final BigDecimal accountBalance1 = invoiceUserApi.getAccountBalance(account.getId(), callContext);
    assertTrue(accountBalance1.compareTo(new BigDecimal("249.95")) == 0);
    final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), false, true, ImmutableList.<PluginProperty>of(), callContext);
    assertEquals(payments.size(), 1);
    assertEquals(payments.get(0).getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
    assertEquals(payments.get(0).getTransactions().size(), 1);
    assertEquals(payments.get(0).getTransactions().get(0).getAmount().compareTo(new BigDecimal("249.95")), 0);
    assertEquals(payments.get(0).getTransactions().get(0).getCurrency(), Currency.USD);
    assertEquals(payments.get(0).getTransactions().get(0).getProcessedAmount().compareTo(BigDecimal.ZERO), 0);
    assertEquals(payments.get(0).getTransactions().get(0).getProcessedCurrency(), Currency.USD);
    assertEquals(payments.get(0).getTransactions().get(0).getTransactionStatus(), TransactionStatus.UNKNOWN);
    assertEquals(payments.get(0).getPaymentAttempts().size(), 1);
    assertEquals(payments.get(0).getPaymentAttempts().get(0).getPluginName(), InvoicePaymentControlPluginApi.PLUGIN_NAME);
    assertEquals(payments.get(0).getPaymentAttempts().get(0).getStateName(), "ABORTED");
    // Verify account transitions to OD1
    addDaysAndCheckForCompletion(2, NextEvent.BLOCK);
    checkODState("OD1", account.getId());
    // Verify we cannot trigger double payments
    try {
        invoicePaymentApi.createPurchaseForInvoicePayment(account, invoice1.getId(), payments.get(0).getPaymentMethodId(), null, invoice1.getBalance(), invoice1.getCurrency(), clock.getUTCNow(), null, null, ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
        Assert.fail();
    } catch (final PaymentApiException e) {
        Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_PLUGIN_API_ABORTED.getCode());
        assertListenerStatus();
    }
    // Transition the payment to success
    final PaymentTransaction existingPaymentTransaction = payments.get(0).getTransactions().get(0);
    final PaymentTransaction updatedPaymentTransaction = Mockito.mock(PaymentTransaction.class);
    Mockito.when(updatedPaymentTransaction.getId()).thenReturn(existingPaymentTransaction.getId());
    Mockito.when(updatedPaymentTransaction.getExternalKey()).thenReturn(existingPaymentTransaction.getExternalKey());
    Mockito.when(updatedPaymentTransaction.getTransactionType()).thenReturn(existingPaymentTransaction.getTransactionType());
    Mockito.when(updatedPaymentTransaction.getProcessedAmount()).thenReturn(new BigDecimal("249.95"));
    Mockito.when(updatedPaymentTransaction.getProcessedCurrency()).thenReturn(existingPaymentTransaction.getCurrency());
    Mockito.when(updatedPaymentTransaction.getAmount()).thenReturn(existingPaymentTransaction.getAmount());
    Mockito.when(updatedPaymentTransaction.getCurrency()).thenReturn(existingPaymentTransaction.getCurrency());
    busHandler.pushExpectedEvents(NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
    adminPaymentApi.fixPaymentTransactionState(payments.get(0), updatedPaymentTransaction, TransactionStatus.SUCCESS, null, null, ImmutableList.<PluginProperty>of(), callContext);
    assertListenerStatus();
    checkODState(OverdueWrapper.CLEAR_STATE_NAME, account.getId());
    final Invoice invoice2 = invoiceUserApi.getInvoice(invoice1.getId(), callContext);
    assertTrue(invoice2.getBalance().compareTo(BigDecimal.ZERO) == 0);
    assertTrue(invoice2.getPaidAmount().compareTo(new BigDecimal("249.95")) == 0);
    assertTrue(invoice2.getChargedAmount().compareTo(new BigDecimal("249.95")) == 0);
    assertEquals(invoice2.getPayments().size(), 1);
    assertEquals(invoice2.getPayments().get(0).getAmount().compareTo(new BigDecimal("249.95")), 0);
    assertEquals(invoice2.getPayments().get(0).getCurrency(), Currency.USD);
    assertTrue(invoice2.getPayments().get(0).isSuccess());
    assertNotNull(invoice2.getPayments().get(0).getPaymentId());
    final BigDecimal accountBalance2 = invoiceUserApi.getAccountBalance(account.getId(), callContext);
    assertTrue(accountBalance2.compareTo(BigDecimal.ZERO) == 0);
    final List<Payment> payments2 = paymentApi.getAccountPayments(account.getId(), false, true, ImmutableList.<PluginProperty>of(), callContext);
    assertEquals(payments2.size(), 1);
    assertEquals(payments2.get(0).getPurchasedAmount().compareTo(new BigDecimal("249.95")), 0);
    assertEquals(payments2.get(0).getTransactions().size(), 1);
    assertEquals(payments2.get(0).getTransactions().get(0).getAmount().compareTo(new BigDecimal("249.95")), 0);
    assertEquals(payments2.get(0).getTransactions().get(0).getCurrency(), Currency.USD);
    assertEquals(payments2.get(0).getTransactions().get(0).getProcessedAmount().compareTo(new BigDecimal("249.95")), 0);
    assertEquals(payments2.get(0).getTransactions().get(0).getProcessedCurrency(), Currency.USD);
    assertEquals(payments2.get(0).getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
    assertEquals(payments2.get(0).getPaymentAttempts().size(), 1);
    assertEquals(payments2.get(0).getPaymentAttempts().get(0).getPluginName(), InvoicePaymentControlPluginApi.PLUGIN_NAME);
    assertEquals(payments2.get(0).getPaymentAttempts().get(0).getStateName(), "SUCCESS");
}
Also used : Account(org.killbill.billing.account.api.Account) Invoice(org.killbill.billing.invoice.api.Invoice) ByteArrayInputStream(java.io.ByteArrayInputStream) InputStream(java.io.InputStream) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) DefaultOverdueConfig(org.killbill.billing.overdue.config.DefaultOverdueConfig) LocalDate(org.joda.time.LocalDate) BigDecimal(java.math.BigDecimal) PaymentTransaction(org.killbill.billing.payment.api.PaymentTransaction) Payment(org.killbill.billing.payment.api.Payment) ByteArrayInputStream(java.io.ByteArrayInputStream) AccountData(org.killbill.billing.account.api.AccountData) DefaultEntitlement(org.killbill.billing.entitlement.api.DefaultEntitlement) Test(org.testng.annotations.Test)

Aggregations

PaymentApiException (org.killbill.billing.payment.api.PaymentApiException)57 UUID (java.util.UUID)20 Test (org.testng.annotations.Test)19 PaymentTransactionModelDao (org.killbill.billing.payment.dao.PaymentTransactionModelDao)14 Account (org.killbill.billing.account.api.Account)12 Payment (org.killbill.billing.payment.api.Payment)12 PaymentAttemptModelDao (org.killbill.billing.payment.dao.PaymentAttemptModelDao)11 BigDecimal (java.math.BigDecimal)10 OperationException (org.killbill.automaton.OperationException)10 PaymentModelDao (org.killbill.billing.payment.dao.PaymentModelDao)9 Predicate (com.google.common.base.Predicate)7 PaymentControlApiException (org.killbill.billing.control.plugin.api.PaymentControlApiException)7 PluginDispatcherReturnType (org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType)7 PaymentPluginApi (org.killbill.billing.payment.plugin.api.PaymentPluginApi)7 LinkedList (java.util.LinkedList)6 AccountApiException (org.killbill.billing.account.api.AccountApiException)6 Invoice (org.killbill.billing.invoice.api.Invoice)6 PluginProperty (org.killbill.billing.payment.api.PluginProperty)6 PaymentPluginApiException (org.killbill.billing.payment.plugin.api.PaymentPluginApiException)6 DateTime (org.joda.time.DateTime)5