use of org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException 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(), null, pluginProperties, paymentControlPluginNames, callContext, internalCallContext);
}
use of org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException 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(), // The amount on the payment attempt drives the amount of the new payment transaction
attempt.getAmount(), attempt.getCurrency(), null, pluginProperties, paymentControlPluginNames, callContext, internalCallContext);
log.debug("retryPaymentTransaction result: payment='{}'", payment);
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='{}'", 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);
}
}
use of org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException in project killbill by killbill.
the class IncompletePaymentAttemptTask method doIteration.
// Since the code is a bit tedious to follow, I'm adding some notes here on where isApiPayment is used (valid as of 09/19/2019 - might become stale!):
// * Passed through to plugins as PaymentControlContext#isApiPayment (e.g. InvoicePaymentControlPluginApi)
// * Used in PaymentEnteringStateCallback to decide whether to send a PaymentErrorInternalEvent
// To ensure that payments are retried, isApiPayment must be true (see https://github.com/killbill/killbill/issues/880).
@VisibleForTesting
public boolean doIteration(final PaymentAttemptModelDao attempt, final boolean isApiPayment) {
// We don't grab account lock here as the lock will be taken when calling the completeRun API.
final InternalTenantContext tenantContext = internalCallContextFactory.createInternalTenantContext(attempt.getTenantRecordId(), attempt.getAccountRecordId());
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(tenantContext.getTenantRecordId(), tenantContext.getAccountRecordId(), "AttemptCompletionJanitorTask", CallOrigin.INTERNAL, UserType.SYSTEM, UUIDs.randomUUID());
final List<PaymentTransactionModelDao> transactions = paymentDao.getPaymentTransactionsByExternalKey(attempt.getTransactionExternalKey(), tenantContext);
final List<PaymentTransactionModelDao> filteredTransactions = ImmutableList.copyOf(Iterables.filter(transactions, new Predicate<PaymentTransactionModelDao>() {
@Override
public boolean apply(final PaymentTransactionModelDao input) {
return input.getAttemptId().equals(attempt.getId());
}
}));
// We only expect at most one transaction for a given attempt, but as a precaution we check for more; if this is the case we log a warn and continue processing the first one.
if (filteredTransactions.size() > 1) {
log.warn("Found {} transactions for paymentAttempt {}", filteredTransactions.size(), attempt.getId());
}
final PaymentTransactionModelDao transaction = filteredTransactions.isEmpty() ? null : filteredTransactions.get(0);
if (transaction == null) {
log.info("Moving attemptId='{}' to ABORTED", attempt.getId());
paymentDao.updatePaymentAttemptWithProperties(attempt.getId(), attempt.getPaymentMethodId(), attempt.getTransactionId(), "ABORTED", // Keep the initial amount, as this will drive the retries
attempt.getAmount(), attempt.getCurrency(), attempt.getPluginProperties(), internalCallContext);
return true;
}
// at which point the attempt can also be transition to a different state.
if (transaction.getTransactionStatus() == TransactionStatus.UNKNOWN) {
return false;
}
try {
log.info("Completing attemptId='{}', stateName='{}'", attempt.getId(), attempt.getStateName());
final Account account = accountInternalApi.getAccountById(attempt.getAccountId(), tenantContext);
final PaymentStateControlContext paymentStateContext = new PaymentStateControlContext(attempt.toPaymentControlPluginNames(), isApiPayment, null, transaction.getPaymentId(), attempt.getPaymentExternalKey(), transaction.getId(), transaction.getTransactionExternalKey(), transaction.getTransactionType(), account, attempt.getPaymentMethodId(), transaction.getAmount(), transaction.getCurrency(), null, PluginPropertySerializer.deserialize(attempt.getPluginProperties()), internalCallContext, internalCallContextFactory.createCallContext(internalCallContext));
// Normally set by leavingState Callback
paymentStateContext.setAttemptId(attempt.getId());
// Normally set by raw state machine
paymentStateContext.setPaymentTransactionModelDao(transaction);
//
// Will rerun the state machine with special callbacks to only make the executePluginOnSuccessCalls / executePluginOnFailureCalls calls
// to the PaymentControlPluginApi plugin and transition the state.
//
pluginControlledPaymentAutomatonRunner.completeRun(paymentStateContext);
return true;
} catch (final AccountApiException e) {
log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
} catch (final PluginPropertySerializerException e) {
log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
} catch (final PaymentApiException e) {
log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
}
return false;
}
use of org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException in project killbill by killbill.
the class IncompletePaymentAttemptTask method doIteration.
@Override
public void doIteration(final PaymentAttemptModelDao attempt) {
// We don't grab account lock here as the lock will be taken when calling the completeRun API.
final InternalTenantContext tenantContext = internalCallContextFactory.createInternalTenantContext(attempt.getTenantRecordId(), attempt.getAccountRecordId());
final CallContext callContext = createCallContext("AttemptCompletionJanitorTask", tenantContext);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(attempt.getAccountId(), callContext);
final List<PaymentTransactionModelDao> transactions = paymentDao.getPaymentTransactionsByExternalKey(attempt.getTransactionExternalKey(), tenantContext);
final List<PaymentTransactionModelDao> filteredTransactions = ImmutableList.copyOf(Iterables.filter(transactions, new Predicate<PaymentTransactionModelDao>() {
@Override
public boolean apply(final PaymentTransactionModelDao input) {
return input.getAttemptId().equals(attempt.getId());
}
}));
// We only expect at most one transaction for a given attempt, but as a precaution we check for more; if this is the case we log a warn and continue processing the first one.
if (filteredTransactions.size() > 1) {
log.warn("Found {} transactions for paymentAttempt {}", filteredTransactions.size(), attempt.getId());
}
final PaymentTransactionModelDao transaction = filteredTransactions.isEmpty() ? null : filteredTransactions.get(0);
if (transaction == null) {
log.info("Moving attemptId='{}' to ABORTED", attempt.getId());
paymentDao.updatePaymentAttempt(attempt.getId(), attempt.getTransactionId(), "ABORTED", internalCallContext);
return;
}
// at which point the attempt can also be transition to a different state.
if (transaction.getTransactionStatus() == TransactionStatus.UNKNOWN) {
return;
}
try {
log.info("Completing attemptId='{}'", attempt.getId());
final Account account = accountInternalApi.getAccountById(attempt.getAccountId(), tenantContext);
// unclear
final boolean isApiPayment = true;
final PaymentStateControlContext paymentStateContext = new PaymentStateControlContext(attempt.toPaymentControlPluginNames(), isApiPayment, null, transaction.getPaymentId(), attempt.getPaymentExternalKey(), transaction.getId(), transaction.getTransactionExternalKey(), transaction.getTransactionType(), account, attempt.getPaymentMethodId(), transaction.getAmount(), transaction.getCurrency(), PluginPropertySerializer.deserialize(attempt.getPluginProperties()), internalCallContext, callContext);
// Normally set by leavingState Callback
paymentStateContext.setAttemptId(attempt.getId());
// Normally set by raw state machine
paymentStateContext.setPaymentTransactionModelDao(transaction);
//
// Will rerun the state machine with special callbacks to only make the executePluginOnSuccessCalls / executePluginOnFailureCalls calls
// to the PaymentControlPluginApi plugin and transition the state.
//
pluginControlledPaymentAutomatonRunner.completeRun(paymentStateContext);
} catch (final AccountApiException e) {
log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
} catch (final PluginPropertySerializerException e) {
log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
} catch (final PaymentApiException e) {
log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
}
}
use of org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException 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);
}
}
}
Aggregations