Search in sources :

Example 21 with PaymentAttemptModelDao

use of org.killbill.billing.payment.dao.PaymentAttemptModelDao in project killbill by killbill.

the class PaymentProcessor method cancelScheduledPaymentTransaction.

public void cancelScheduledPaymentTransaction(@Nullable final UUID paymentTransactionId, @Nullable final String paymentTransactionExternalKey, final CallContext callContext) throws PaymentApiException {
    final InternalCallContext internalCallContextWithoutAccountRecordId = internalCallContextFactory.createInternalCallContextWithoutAccountRecordId(callContext);
    final String effectivePaymentTransactionExternalKey;
    if (paymentTransactionExternalKey == null) {
        final PaymentTransactionModelDao transaction = paymentDao.getPaymentTransaction(paymentTransactionId, internalCallContextWithoutAccountRecordId);
        effectivePaymentTransactionExternalKey = transaction.getTransactionExternalKey();
    } else {
        effectivePaymentTransactionExternalKey = paymentTransactionExternalKey;
    }
    final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttemptByTransactionExternalKey(effectivePaymentTransactionExternalKey, internalCallContextWithoutAccountRecordId);
    if (attempts.isEmpty()) {
        return;
    }
    final PaymentAttemptModelDao lastPaymentAttempt = attempts.get(attempts.size() - 1);
    final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(lastPaymentAttempt.getAccountId(), callContext);
    cancelScheduledPaymentTransaction(lastPaymentAttempt.getId(), internalCallContext);
}
Also used : PaymentAttemptModelDao(org.killbill.billing.payment.dao.PaymentAttemptModelDao) PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext)

Example 22 with PaymentAttemptModelDao

use of org.killbill.billing.payment.dao.PaymentAttemptModelDao in project killbill by killbill.

the class DefaultControlInitiated method leavingState.

@Override
public void leavingState(final State state) throws OperationException {
    final DateTime utcNow = pluginControlPaymentAutomatonRunner.getClock().getUTCNow();
    // Retrieve the associated payment transaction, if any
    PaymentTransactionModelDao paymentTransactionModelDaoCandidate = null;
    if (stateContext.getTransactionId() != null) {
        paymentTransactionModelDaoCandidate = paymentDao.getPaymentTransaction(stateContext.getTransactionId(), stateContext.getInternalCallContext());
        Preconditions.checkNotNull(paymentTransactionModelDaoCandidate, "paymentTransaction cannot be null for id " + stateContext.getTransactionId());
    } else if (stateContext.getPaymentTransactionExternalKey() != null) {
        final List<PaymentTransactionModelDao> paymentTransactionModelDaos = paymentDao.getPaymentTransactionsByExternalKey(stateContext.getPaymentTransactionExternalKey(), stateContext.getInternalCallContext());
        if (!paymentTransactionModelDaos.isEmpty()) {
            paymentTransactionModelDaoCandidate = paymentTransactionModelDaos.get(paymentTransactionModelDaos.size() - 1);
        }
    }
    final PaymentTransactionModelDao paymentTransactionModelDao = paymentTransactionModelDaoCandidate != null && TRANSIENT_TRANSACTION_STATUSES.contains(paymentTransactionModelDaoCandidate.getTransactionStatus()) ? paymentTransactionModelDaoCandidate : null;
    if (stateContext.getPaymentId() != null && stateContext.getPaymentExternalKey() == null) {
        final PaymentModelDao payment = paymentDao.getPayment(stateContext.getPaymentId(), stateContext.getInternalCallContext());
        Preconditions.checkNotNull(payment, "payment cannot be null for id " + stateContext.getPaymentId());
        stateContext.setPaymentExternalKey(payment.getExternalKey());
        stateContext.setPaymentMethodId(payment.getPaymentMethodId());
    } else if (stateContext.getPaymentExternalKey() == null) {
        final UUID paymentIdForNewPayment = UUIDs.randomUUID();
        stateContext.setPaymentIdForNewPayment(paymentIdForNewPayment);
        stateContext.setPaymentExternalKey(paymentIdForNewPayment.toString());
    }
    if (paymentTransactionModelDao != null) {
        stateContext.setPaymentTransactionModelDao(paymentTransactionModelDao);
        stateContext.setProcessedAmount(paymentTransactionModelDao.getProcessedAmount());
        stateContext.setProcessedCurrency(paymentTransactionModelDao.getProcessedCurrency());
    } else if (stateContext.getPaymentTransactionExternalKey() == null) {
        final UUID paymentTransactionIdForNewPaymentTransaction = UUIDs.randomUUID();
        stateContext.setPaymentTransactionIdForNewPaymentTransaction(paymentTransactionIdForNewPaymentTransaction);
        stateContext.setPaymentTransactionExternalKey(paymentTransactionIdForNewPaymentTransaction.toString());
    }
    // In this case, we also want to provide the associated plugin name
    if (stateContext.getPaymentMethodId() != null) {
        final PaymentMethodModelDao pm = paymentDao.getPaymentMethod(stateContext.getPaymentMethodId(), stateContext.getInternalCallContext());
        // Payment method was deleted
        if (pm != null) {
            stateContext.setOriginalPaymentPluginName(pm.getPluginName());
        }
    }
    if (state.getName().equals(initialState.getName()) || state.getName().equals(retriedState.getName())) {
        try {
            final PaymentAttemptModelDao attempt;
            if (paymentTransactionModelDao != null && paymentTransactionModelDao.getAttemptId() != null) {
                attempt = pluginControlPaymentAutomatonRunner.getPaymentDao().getPaymentAttempt(paymentTransactionModelDao.getAttemptId(), stateContext.getInternalCallContext());
                Preconditions.checkNotNull(attempt, "attempt cannot be null for id " + paymentTransactionModelDao.getAttemptId());
            } else {
                // 
                // We don't serialize any properties at this stage to avoid serializing sensitive information.
                // However, if after going through the control plugins, the attempt end up in RETRIED state,
                // the properties will be serialized in the enteringState callback (any plugin that sets a
                // retried date is responsible to correctly remove sensitive information such as CVV, ...)
                // 
                final byte[] serializedProperties = PluginPropertySerializer.serialize(ImmutableList.<PluginProperty>of());
                attempt = new PaymentAttemptModelDao(stateContext.getAccount().getId(), stateContext.getPaymentMethodId(), utcNow, utcNow, stateContext.getPaymentExternalKey(), stateContext.getTransactionId(), stateContext.getPaymentTransactionExternalKey(), transactionType, initialState.getName(), stateContext.getAmount(), stateContext.getCurrency(), stateContext.getPaymentControlPluginNames(), serializedProperties);
                pluginControlPaymentAutomatonRunner.getPaymentDao().insertPaymentAttemptWithProperties(attempt, stateContext.getInternalCallContext());
            }
            stateContext.setAttemptId(attempt.getId());
        } catch (final PluginPropertySerializerException e) {
            throw new OperationException(e);
        }
    }
}
Also used : PaymentAttemptModelDao(org.killbill.billing.payment.dao.PaymentAttemptModelDao) PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) PaymentModelDao(org.killbill.billing.payment.dao.PaymentModelDao) PluginPropertySerializerException(org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException) PaymentMethodModelDao(org.killbill.billing.payment.dao.PaymentMethodModelDao) List(java.util.List) ImmutableList(com.google.common.collect.ImmutableList) UUID(java.util.UUID) DateTime(org.joda.time.DateTime) OperationException(org.killbill.automaton.OperationException)

Example 23 with PaymentAttemptModelDao

use of org.killbill.billing.payment.dao.PaymentAttemptModelDao in project killbill by killbill.

the class DefaultControlCompleted method enteringState.

@Override
public void enteringState(final State state, final OperationCallback operationCallback, final OperationResult operationResult, final LeavingStateCallback leavingStateCallback) {
    final PaymentAttemptModelDao attempt = retryablePaymentAutomatonRunner.getPaymentDao().getPaymentAttempt(paymentStateContext.getAttemptId(), paymentStateContext.getInternalCallContext());
    final UUID transactionId = paymentStateContext.getCurrentTransaction() != null ? paymentStateContext.getCurrentTransaction().getId() : null;
    logger.debug("enteringState attemptId='{}', transactionId='{}', state='{}'", attempt.getId(), transactionId, state.getName());
    // At this stage we can update the paymentAttempt state AND serialize the plugin properties. Control plugins will have had the opportunity to erase sensitive data if required.
    retryablePaymentAutomatonRunner.getPaymentDao().updatePaymentAttemptWithProperties(attempt.getId(), paymentStateContext.getPaymentMethodId(), transactionId, state.getName(), // not the processed amount, as this will drive the retries in case of failure
    paymentStateContext.getAmount(), paymentStateContext.getCurrency(), getSerializedProperties(), paymentStateContext.getInternalCallContext());
    if (retriedState.getName().equals(state.getName()) && !isUnknownTransaction()) {
        retryServiceScheduler.scheduleRetry(ObjectType.PAYMENT_ATTEMPT, attempt.getId(), attempt.getId(), attempt.getTenantRecordId(), paymentStateContext.getPaymentControlPluginNames(), paymentStateContext.getRetryDate());
    }
}
Also used : PaymentAttemptModelDao(org.killbill.billing.payment.dao.PaymentAttemptModelDao) UUID(java.util.UUID)

Example 24 with PaymentAttemptModelDao

use of org.killbill.billing.payment.dao.PaymentAttemptModelDao in project killbill by killbill.

the class TestJanitor method testCreateSuccessRefundPaymentControlWithItemAdjustments.

@Test(groups = "slow")
public void testCreateSuccessRefundPaymentControlWithItemAdjustments() throws Exception {
    final BigDecimal requestedAmount = BigDecimal.TEN;
    final UUID subscriptionId = UUID.randomUUID();
    final UUID bundleId = UUID.randomUUID();
    final LocalDate now = clock.getUTCToday();
    testListener.pushExpectedEvent(NextEvent.INVOICE);
    final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
    testListener.assertListenerStatus();
    final String paymentExternalKey = invoice.getId().toString();
    final String transactionExternalKey = "craboom";
    final String transactionExternalKey2 = "qwerty";
    final InvoiceItem invoiceItem = new MockRecurringInvoiceItem(invoice.getId(), account.getId(), subscriptionId, bundleId, "test plan", "test phase", null, now, now.plusMonths(1), requestedAmount, new BigDecimal("1.0"), Currency.USD);
    invoice.addInvoiceItem(invoiceItem);
    testListener.pushExpectedEvent(NextEvent.PAYMENT);
    invoicePaymentApi.createPurchaseForInvoicePayment(account, invoice.getId(), account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null, paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), INVOICE_PAYMENT, callContext);
    final Payment payment = paymentApi.getPaymentByExternalKey(paymentExternalKey, false, false, ImmutableList.<PluginProperty>of(), callContext);
    testListener.assertListenerStatus();
    final List<PluginProperty> refundProperties = new ArrayList<PluginProperty>();
    final HashMap<UUID, BigDecimal> uuidBigDecimalHashMap = new HashMap<UUID, BigDecimal>();
    uuidBigDecimalHashMap.put(invoiceItem.getId(), new BigDecimal("1.0"));
    testListener.pushExpectedEvent(NextEvent.PAYMENT);
    invoicePaymentApi.createRefundForInvoicePayment(false, uuidBigDecimalHashMap, account, payment.getId(), null, Currency.USD, null, transactionExternalKey2, refundProperties, INVOICE_PAYMENT, callContext);
    final Payment payment2 = paymentApi.getPayment(payment.getId(), false, false, refundProperties, callContext);
    testListener.assertListenerStatus();
    assertEquals(payment2.getTransactions().size(), 2);
    PaymentTransaction refundTransaction = payment2.getTransactions().get(1);
    assertEquals(refundTransaction.getTransactionType(), TransactionType.REFUND);
    final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
    assertEquals(attempts.size(), 2);
    final PaymentAttemptModelDao refundAttempt = attempts.get(1);
    assertEquals(refundAttempt.getTransactionType(), TransactionType.REFUND);
    assertEquals(refundAttempt.getAmount().compareTo(BigDecimal.ONE), 0);
    assertEquals(refundAttempt.getCurrency(), Currency.USD);
    // Ok now the fun part starts... we modify the attempt state to be 'INIT' and wait the the Janitor to do its job.
    paymentDao.updatePaymentAttemptWithProperties(refundAttempt.getId(), refundAttempt.getPaymentMethodId(), refundAttempt.getTransactionId(), "INIT", null, null, null, internalCallContext);
    final PaymentAttemptModelDao attempt2 = paymentDao.getPaymentAttempt(refundAttempt.getId(), internalCallContext);
    assertEquals(attempt2.getStateName(), "INIT");
    assertNull(attempt2.getAmount());
    assertNull(attempt2.getCurrency());
    clock.addDays(1);
    await().atMost(TIMEOUT, TimeUnit.SECONDS).until(new Callable<Boolean>() {

        @Override
        public Boolean call() throws Exception {
            final PaymentAttemptModelDao attempt3 = paymentDao.getPaymentAttempt(refundAttempt.getId(), internalCallContext);
            return "SUCCESS".equals(attempt3.getStateName());
        }
    });
    final PaymentAttemptModelDao refundAttempt2 = paymentDao.getPaymentAttempt(refundAttempt.getId(), internalCallContext);
    assertEquals(refundAttempt2.getStateName(), "SUCCESS");
    assertEquals(refundAttempt2.getAmount().compareTo(BigDecimal.ONE), 0);
    assertEquals(refundAttempt2.getCurrency(), Currency.USD);
}
Also used : PaymentAttemptModelDao(org.killbill.billing.payment.dao.PaymentAttemptModelDao) Invoice(org.killbill.billing.invoice.api.Invoice) InvoiceItem(org.killbill.billing.invoice.api.InvoiceItem) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) LocalDate(org.joda.time.LocalDate) BigDecimal(java.math.BigDecimal) InvoiceApiException(org.killbill.billing.invoice.api.InvoiceApiException) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) EventBusException(org.killbill.bus.api.PersistentBus.EventBusException) PaymentTransaction(org.killbill.billing.payment.api.PaymentTransaction) PluginProperty(org.killbill.billing.payment.api.PluginProperty) Payment(org.killbill.billing.payment.api.Payment) UUID(java.util.UUID) Test(org.testng.annotations.Test)

Example 25 with PaymentAttemptModelDao

use of org.killbill.billing.payment.dao.PaymentAttemptModelDao in project killbill by killbill.

the class TestRetryService method testFailedPaymentWithLastRetrySuccess.

@Test(groups = "fast")
public void testFailedPaymentWithLastRetrySuccess() throws Exception {
    final Account account = testHelper.createTestAccount("yiyi.gmail.com", true);
    final Invoice invoice = testHelper.createTestInvoice(account, clock.getUTCToday(), Currency.USD);
    final BigDecimal amount = new BigDecimal("10.00");
    final UUID subscriptionId = UUID.randomUUID();
    final UUID bundleId = UUID.randomUUID();
    final LocalDate startDate = clock.getUTCToday();
    final LocalDate endDate = startDate.plusMonths(1);
    invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(), account.getId(), subscriptionId, bundleId, "test plan", "test phase", null, startDate, endDate, amount, new BigDecimal("1.0"), Currency.USD));
    setPaymentFailure(FailureType.PAYMENT_FAILURE);
    final String paymentExternalKey = UUID.randomUUID().toString();
    final String transactionExternalKey = UUID.randomUUID().toString();
    invoicePaymentInternalApi.createPurchaseForInvoicePayment(false, account, invoice.getId(), account.getPaymentMethodId(), null, amount, Currency.USD, null, paymentExternalKey, transactionExternalKey, NO_PROPERTIES, PAYMENT_OPTIONS, internalCallContext);
    Payment payment = getPaymentForExternalKey(paymentExternalKey);
    List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
    assertEquals(attempts.size(), 1);
    final List<PaymentTransactionModelDao> transactions = paymentDao.getTransactionsForPayment(payment.getId(), internalCallContext);
    assertEquals(transactions.size(), 1);
    int maxTries = paymentConfig.getPaymentFailureRetryDays(internalCallContext).size();
    for (int curFailure = 0; curFailure < maxTries; curFailure++) {
        // Set plugin to fail with specific type unless this is the last attempt and we want a success
        if (curFailure < (maxTries - 1)) {
            setPaymentFailure(FailureType.PAYMENT_FAILURE);
        }
        moveClockForFailureType(FailureType.PAYMENT_FAILURE, curFailure);
        final int curFailureCondition = curFailure;
        await().atMost(TIMEOUT, SECONDS).until(new Callable<Boolean>() {

            @Override
            public Boolean call() throws Exception {
                final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
                final List<PaymentAttemptModelDao> filteredAttempts = ImmutableList.copyOf(Iterables.filter(attempts, new Predicate<PaymentAttemptModelDao>() {

                    @Override
                    public boolean apply(final PaymentAttemptModelDao input) {
                        return input.getStateName().equals("SUCCESS") || input.getStateName().equals("RETRIED") || input.getStateName().equals("ABORTED");
                    }
                }));
                return filteredAttempts.size() == curFailureCondition + 2;
            }
        });
    }
    attempts = paymentDao.getPaymentAttempts(payment.getExternalKey(), internalCallContext);
    final int expectedAttempts = maxTries + 1;
    assertEquals(attempts.size(), expectedAttempts);
    Collections.sort(attempts, new Comparator<PaymentAttemptModelDao>() {

        @Override
        public int compare(final PaymentAttemptModelDao o1, final PaymentAttemptModelDao o2) {
            return o1.getCreatedDate().compareTo(o2.getCreatedDate());
        }
    });
    for (int i = 0; i < attempts.size(); i++) {
        final PaymentAttemptModelDao cur = attempts.get(i);
        if (i < attempts.size() - 1) {
            assertEquals(cur.getStateName(), "RETRIED");
        } else {
            assertEquals(cur.getStateName(), "SUCCESS");
        }
    }
}
Also used : PaymentAttemptModelDao(org.killbill.billing.payment.dao.PaymentAttemptModelDao) Account(org.killbill.billing.account.api.Account) Invoice(org.killbill.billing.invoice.api.Invoice) LocalDate(org.joda.time.LocalDate) BigDecimal(java.math.BigDecimal) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) Payment(org.killbill.billing.payment.api.Payment) PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) ImmutableList(com.google.common.collect.ImmutableList) List(java.util.List) UUID(java.util.UUID) Test(org.testng.annotations.Test)

Aggregations

PaymentAttemptModelDao (org.killbill.billing.payment.dao.PaymentAttemptModelDao)33 Test (org.testng.annotations.Test)24 UUID (java.util.UUID)16 PaymentApiException (org.killbill.billing.payment.api.PaymentApiException)15 PaymentTransactionModelDao (org.killbill.billing.payment.dao.PaymentTransactionModelDao)12 BigDecimal (java.math.BigDecimal)10 LocalDate (org.joda.time.LocalDate)10 Invoice (org.killbill.billing.invoice.api.Invoice)10 Account (org.killbill.billing.account.api.Account)8 Payment (org.killbill.billing.payment.api.Payment)8 DateTime (org.joda.time.DateTime)7 State (org.killbill.automaton.State)7 Predicate (com.google.common.base.Predicate)6 ArrayList (java.util.ArrayList)6 PaymentModelDao (org.killbill.billing.payment.dao.PaymentModelDao)6 ImmutableList (com.google.common.collect.ImmutableList)5 List (java.util.List)5 AccountData (org.killbill.billing.account.api.AccountData)3 ExpectedInvoiceItemCheck (org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck)3 InternalCallContext (org.killbill.billing.callcontext.InternalCallContext)3