use of org.killbill.billing.payment.dao.PaymentAttemptModelDao in project killbill by killbill.
the class TestInvoicePayment method testWithoutDefaultPaymentMethod.
@Test(groups = "slow")
public void testWithoutDefaultPaymentMethod() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
final AccountData accountData = getAccountData(0);
final Account account = createAccount(accountData);
accountChecker.checkAccount(account.getId(), accountData, callContext);
final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
final Invoice invoice1 = invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
// No invoice payment
Assert.assertEquals(invoice1.getPayments().size(), 0);
// There is one dangling payment attempt (not easy to get to...)
final List<AuditLog> paymentAttemptsAuditLogs1 = new ArrayList<AuditLog>();
for (final AuditLog auditLog : auditUserApi.getAccountAuditLogs(account.getId(), AuditLevel.FULL, callContext).getAuditLogs()) {
if (auditLog.getAuditedObjectType() == ObjectType.PAYMENT_ATTEMPT) {
paymentAttemptsAuditLogs1.add(auditLog);
}
}
// One INSERT and one UPDATE
Assert.assertEquals(paymentAttemptsAuditLogs1.size(), 2);
final PaymentAttemptModelDao paymentAttempt1 = paymentDao.getPaymentAttempt(paymentAttemptsAuditLogs1.get(0).getAuditedEntityId(), internalCallContext);
Assert.assertEquals(paymentAttempt1.getStateName(), "ABORTED");
// 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
// Note that there is a INVOICE_PAYMENT_ERROR event in that case, unlike the AUTO_PAY_OFF/MANUAL_PAY usecase
addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT_ERROR);
Invoice invoice2 = invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
// Invoice is not paid
Assert.assertEquals(paymentApi.getAccountPayments(account.getId(), false, false, ImmutableList.<PluginProperty>of(), callContext).size(), 0);
Assert.assertEquals(invoice2.getBalance().compareTo(new BigDecimal("249.95")), 0);
Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
// There is no invoice payment
Assert.assertEquals(invoice2.getPayments().size(), 0);
// There is another dangling payment attempt (not easy to get to...)
final List<AuditLog> paymentAttemptsAuditLogs2 = new ArrayList<AuditLog>();
for (final AuditLog auditLog : auditUserApi.getAccountAuditLogs(account.getId(), AuditLevel.FULL, callContext).getAuditLogs()) {
if (auditLog.getAuditedObjectType() == ObjectType.PAYMENT_ATTEMPT && !paymentAttemptsAuditLogs1.contains(auditLog)) {
paymentAttemptsAuditLogs2.add(auditLog);
}
}
// One INSERT and one UPDATE
Assert.assertEquals(paymentAttemptsAuditLogs2.size(), 2);
final PaymentAttemptModelDao paymentAttempt2 = paymentDao.getPaymentAttempt(paymentAttemptsAuditLogs2.get(0).getAuditedEntityId(), internalCallContext);
Assert.assertEquals(paymentAttempt2.getStateName(), "ABORTED");
// Just verify we've found the right one
Assert.assertNotEquals(paymentAttempt2.getId(), paymentAttempt1.getId());
}
use of org.killbill.billing.payment.dao.PaymentAttemptModelDao in project killbill by killbill.
the class TestInvoicePayment method testAUTO_PAY_OFFThenPartialPayment.
@Test(groups = "slow")
public void testAUTO_PAY_OFFThenPartialPayment() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
final AccountData accountData = getAccountData(0);
final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
accountChecker.checkAccount(account.getId(), accountData, callContext);
final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
final Invoice invoice1 = invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
// No invoice payment
Assert.assertEquals(invoice1.getPayments().size(), 0);
// There is one dangling payment attempt (not easy to get to...)
final List<AuditLog> paymentAttemptsAuditLogs1 = new ArrayList<AuditLog>();
for (final AuditLog auditLog : auditUserApi.getAccountAuditLogs(account.getId(), AuditLevel.FULL, callContext).getAuditLogs()) {
if (auditLog.getAuditedObjectType() == ObjectType.PAYMENT_ATTEMPT) {
paymentAttemptsAuditLogs1.add(auditLog);
}
}
// One INSERT and one UPDATE
Assert.assertEquals(paymentAttemptsAuditLogs1.size(), 2);
final PaymentAttemptModelDao paymentAttempt1 = paymentDao.getPaymentAttempt(paymentAttemptsAuditLogs1.get(0).getAuditedEntityId(), internalCallContext);
Assert.assertEquals(paymentAttempt1.getStateName(), "ABORTED");
// Put the account in AUTO_PAY_OFF to make sure payment system does not try to pay the initial invoice
add_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
// 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE);
Invoice invoice2 = invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
// Invoice is not paid
Assert.assertEquals(paymentApi.getAccountPayments(account.getId(), false, false, ImmutableList.<PluginProperty>of(), callContext).size(), 0);
Assert.assertEquals(invoice2.getBalance().compareTo(new BigDecimal("249.95")), 0);
Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
// No invoice payment
Assert.assertEquals(invoice2.getPayments().size(), 0);
// There is another dangling payment attempt (not easy to get to...)
final List<AuditLog> paymentAttemptsAuditLogs2 = new ArrayList<AuditLog>();
for (final AuditLog auditLog : auditUserApi.getAccountAuditLogs(account.getId(), AuditLevel.FULL, callContext).getAuditLogs()) {
if (auditLog.getAuditedObjectType() == ObjectType.PAYMENT_ATTEMPT && !paymentAttemptsAuditLogs1.contains(auditLog)) {
paymentAttemptsAuditLogs2.add(auditLog);
}
}
// One INSERT and one UPDATE
Assert.assertEquals(paymentAttemptsAuditLogs2.size(), 2);
final PaymentAttemptModelDao paymentAttempt2 = paymentDao.getPaymentAttempt(paymentAttemptsAuditLogs2.get(0).getAuditedEntityId(), internalCallContext);
Assert.assertEquals(paymentAttempt2.getStateName(), "ABORTED");
// Just verify we've found the right one
Assert.assertNotEquals(paymentAttempt2.getId(), paymentAttempt1.getId());
// Trigger partial payment
final Payment payment1 = createPaymentAndCheckForCompletion(account, invoice2, BigDecimal.TEN, account.getCurrency(), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 5, 31), BigDecimal.TEN, TransactionStatus.SUCCESS, invoice2.getId(), Currency.USD));
Assert.assertEquals(payment1.getTransactions().size(), 1);
Assert.assertEquals(payment1.getPurchasedAmount().compareTo(BigDecimal.TEN), 0);
Assert.assertEquals(payment1.getTransactions().get(0).getProcessedAmount().compareTo(BigDecimal.TEN), 0);
invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
Assert.assertEquals(invoice2.getBalance().compareTo(new BigDecimal("239.95")), 0);
Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
// Remove AUTO_PAY_OFF and verify the invoice is fully paid
remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
final Payment payment2 = paymentChecker.checkPayment(account.getId(), 2, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 5, 31), new BigDecimal("239.95"), TransactionStatus.SUCCESS, invoice2.getId(), Currency.USD));
Assert.assertEquals(payment2.getTransactions().size(), 1);
Assert.assertEquals(payment2.getPurchasedAmount().compareTo(new BigDecimal("239.95")), 0);
Assert.assertEquals(payment2.getTransactions().get(0).getProcessedAmount().compareTo(new BigDecimal("239.95")), 0);
invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
Assert.assertEquals(invoice2.getBalance().compareTo(BigDecimal.ZERO), 0);
Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
}
use of org.killbill.billing.payment.dao.PaymentAttemptModelDao in project killbill by killbill.
the class PaymentRefresher method getPaymentAttempts.
private List<PaymentAttempt> getPaymentAttempts(final List<PaymentAttemptModelDao> pastPaymentAttempts, final InternalTenantContext internalTenantContext) {
final List<PaymentAttempt> paymentAttempts = new ArrayList<PaymentAttempt>();
// Add Past Payment Attempts
for (final PaymentAttemptModelDao pastPaymentAttempt : pastPaymentAttempts) {
final PaymentAttempt paymentAttempt = new DefaultPaymentAttempt(pastPaymentAttempt.getAccountId(), pastPaymentAttempt.getPaymentMethodId(), pastPaymentAttempt.getId(), pastPaymentAttempt.getCreatedDate(), pastPaymentAttempt.getUpdatedDate(), pastPaymentAttempt.getCreatedDate(), pastPaymentAttempt.getPaymentExternalKey(), pastPaymentAttempt.getTransactionId(), pastPaymentAttempt.getTransactionExternalKey(), pastPaymentAttempt.getTransactionType(), pastPaymentAttempt.getStateName(), pastPaymentAttempt.getAmount(), pastPaymentAttempt.getCurrency(), pastPaymentAttempt.getPluginName(), buildPluginProperties(pastPaymentAttempt));
paymentAttempts.add(paymentAttempt);
}
// Get Future Payment Attempts from Notification Queue and add them to the list
try {
final NotificationQueue retryQueue = notificationQueueService.getNotificationQueue(KILLBILL_SERVICES.PAYMENT_SERVICE.getServiceName(), DefaultRetryService.QUEUE_NAME);
final Iterable<NotificationEventWithMetadata<NotificationEvent>> notificationEventWithMetadatas = retryQueue.getFutureNotificationForSearchKeys(internalTenantContext.getAccountRecordId(), internalTenantContext.getTenantRecordId());
for (final NotificationEventWithMetadata<NotificationEvent> notificationEvent : notificationEventWithMetadatas) {
// Last Attempt
final PaymentAttemptModelDao lastPaymentAttempt = getLastPaymentAttempt(pastPaymentAttempts, ((PaymentRetryNotificationKey) notificationEvent.getEvent()).getAttemptId());
if (lastPaymentAttempt != null) {
final PaymentAttempt futurePaymentAttempt = new // accountId
DefaultPaymentAttempt(// accountId
lastPaymentAttempt.getAccountId(), // paymentMethodId
lastPaymentAttempt.getPaymentMethodId(), // id
((PaymentRetryNotificationKey) notificationEvent.getEvent()).getAttemptId(), // createdDate
null, // updatedDate
null, // effectiveDate
notificationEvent.getEffectiveDate(), // paymentExternalKey
lastPaymentAttempt.getPaymentExternalKey(), // transactionId
null, // transactionExternalKey
lastPaymentAttempt.getTransactionExternalKey(), // transactionType
lastPaymentAttempt.getTransactionType(), // stateName
SCHEDULED, // amount
lastPaymentAttempt.getAmount(), // currency
lastPaymentAttempt.getCurrency(), // pluginName,
((PaymentRetryNotificationKey) notificationEvent.getEvent()).getPaymentControlPluginNames().get(0), // pluginProperties
buildPluginProperties(lastPaymentAttempt));
paymentAttempts.add(futurePaymentAttempt);
}
}
} catch (final NoSuchNotificationQueue noSuchNotificationQueue) {
log.error("ERROR Loading Notification Queue - " + noSuchNotificationQueue.getMessage());
}
return paymentAttempts;
}
use of org.killbill.billing.payment.dao.PaymentAttemptModelDao in project killbill by killbill.
the class IncompletePaymentAttemptTask method updatePaymentAndTransactionIfNeeded.
private boolean updatePaymentAndTransactionIfNeeded(final UUID accountId, final UUID paymentTransactionId, @Nullable final TransactionStatus currentTransactionStatus, @Nullable final PaymentTransactionInfoPlugin paymentTransactionInfoPlugin, @Nullable final Integer attemptNumber, @Nullable final UUID userToken, final boolean isApiPayment, final InternalTenantContext internalTenantContext) throws LockFailedException {
// First, fix the transaction itself
final TransactionStatus latestTransactionStatus = incompletePaymentTransactionTask.updatePaymentAndTransactionIfNeeded2(accountId, paymentTransactionId, currentTransactionStatus, paymentTransactionInfoPlugin, isApiPayment, internalTenantContext);
final boolean hasTransactionChanged = latestTransactionStatus == null;
// Don't insert a notification for the on-the-fly Janitor
final boolean shouldInsertNotification = attemptNumber != null;
if (!hasTransactionChanged && shouldInsertNotification) {
insertNewNotificationForUnresolvedTransactionIfNeeded(paymentTransactionId, latestTransactionStatus, attemptNumber, userToken, isApiPayment, internalTenantContext.getAccountRecordId(), internalTenantContext.getTenantRecordId());
}
// If there is a payment attempt associated with that transaction, we need to update it as well
boolean hasAttemptChanged = false;
final PaymentTransactionModelDao paymentTransactionModelDao = paymentDao.getPaymentTransaction(paymentTransactionId, internalTenantContext);
if (paymentTransactionModelDao.getAttemptId() != null) {
final PaymentAttemptModelDao paymentAttemptModelDao = paymentDao.getPaymentAttempt(paymentTransactionModelDao.getAttemptId(), internalTenantContext);
if (paymentAttemptModelDao != null) {
if (hasTransactionChanged || retrySMHelper.getInitialState().getName().equals(paymentAttemptModelDao.getStateName())) {
// Run the completion part of the state machine to call the plugins and update the attempt in the right terminal state)
hasAttemptChanged = doIteration(paymentAttemptModelDao, isApiPayment);
}
}
}
return hasTransactionChanged || hasAttemptChanged;
}
use of org.killbill.billing.payment.dao.PaymentAttemptModelDao in project killbill by killbill.
the class DefaultAdminPaymentApi method fixPaymentTransactionState.
// Very similar implementation as the Janitor (see IncompletePaymentTransactionTask / IncompletePaymentAttemptTask)
// The code is different enough to make it difficult to share unfortunately
@Override
public void fixPaymentTransactionState(final Payment payment, final PaymentTransaction paymentTransaction, @Nullable final TransactionStatus transactionStatusOrNull, @Nullable final String lastSuccessPaymentStateOrNull, @Nullable final String currentPaymentStateNameOrNull, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(payment.getAccountId(), callContext);
TransactionStatus transactionStatus = transactionStatusOrNull;
if (transactionStatusOrNull == null) {
checkNotNullParameter(paymentTransaction.getPaymentInfoPlugin(), "PaymentTransactionInfoPlugin");
transactionStatus = PaymentTransactionInfoPluginConverter.toTransactionStatus(paymentTransaction.getPaymentInfoPlugin());
}
String currentPaymentStateName = currentPaymentStateNameOrNull;
if (currentPaymentStateName == null) {
switch(transactionStatus) {
case PENDING:
currentPaymentStateName = paymentSMHelper.getPendingStateForTransaction(paymentTransaction.getTransactionType());
break;
case SUCCESS:
currentPaymentStateName = paymentSMHelper.getSuccessfulStateForTransaction(paymentTransaction.getTransactionType());
break;
case PAYMENT_FAILURE:
currentPaymentStateName = paymentSMHelper.getFailureStateForTransaction(paymentTransaction.getTransactionType());
break;
case PLUGIN_FAILURE:
case UNKNOWN:
default:
currentPaymentStateName = paymentSMHelper.getErroredStateForTransaction(paymentTransaction.getTransactionType());
break;
}
}
String lastSuccessPaymentState = lastSuccessPaymentStateOrNull;
if (lastSuccessPaymentState == null && // Verify we are not updating an older transaction (only the last one has an impact on lastSuccessPaymentState)
paymentTransaction.getId().equals(payment.getTransactions().get(payment.getTransactions().size() - 1).getId())) {
if (paymentSMHelper.isSuccessState(currentPaymentStateName)) {
lastSuccessPaymentState = currentPaymentStateName;
} else {
for (int i = payment.getTransactions().size() - 2; i >= 0; i--) {
final PaymentTransaction transaction = payment.getTransactions().get(i);
if (TransactionStatus.SUCCESS.equals(transaction.getTransactionStatus())) {
lastSuccessPaymentState = paymentSMHelper.getSuccessfulStateForTransaction(transaction.getTransactionType());
break;
}
}
}
}
final PaymentAutomatonDAOHelper paymentAutomatonDAOHelper = new PaymentAutomatonDAOHelper(paymentDao, internalCallContext, paymentSMHelper);
paymentAutomatonDAOHelper.processPaymentInfoPlugin(transactionStatus, paymentTransaction.getPaymentInfoPlugin(), currentPaymentStateName, lastSuccessPaymentState, // See https://github.com/killbill/killbill/issues/1061#issuecomment-521911301
paymentTransaction.getAmount(), paymentTransaction.getCurrency(), payment.getAccountId(), null, payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionType(), true, true);
// If there is a payment attempt associated with that transaction, we need to update it as well
final List<PaymentAttemptModelDao> paymentAttemptsModelDao = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransaction.getExternalKey(), internalCallContext);
final PaymentAttemptModelDao paymentAttemptModelDao = Iterables.<PaymentAttemptModelDao>tryFind(paymentAttemptsModelDao, new Predicate<PaymentAttemptModelDao>() {
@Override
public boolean apply(final PaymentAttemptModelDao input) {
return paymentTransaction.getId().equals(input.getTransactionId());
}
}).orNull();
if (paymentAttemptModelDao != null) {
// We can re-use the logic from IncompletePaymentAttemptTask as it is doing very similar work (i.e. run the completion part of
// the state machine to call the plugins and update the attempt in the right terminal state)
incompletePaymentAttemptTask.doIteration(paymentAttemptModelDao, true);
}
}
Aggregations