use of org.killbill.billing.control.plugin.api.PaymentControlApiException in project killbill by killbill.
the class PluginControlPaymentAutomatonRunner method completeRun.
public Payment completeRun(final PaymentStateControlContext paymentStateContext) throws PaymentApiException {
try {
final OperationCallback callback = new CompletionControlOperation(locker, paymentPluginDispatcher, paymentConfig, paymentStateContext, paymentProcessor, controlPluginRunner);
final LeavingStateCallback leavingStateCallback = new NoopControlInitiated();
final EnteringStateCallback enteringStateCallback = new DefaultControlCompleted(this, paymentStateContext, paymentControlStateMachineHelper.getRetriedState(), retryServiceScheduler);
paymentControlStateMachineHelper.getInitialState().runOperation(paymentControlStateMachineHelper.getOperation(), callback, enteringStateCallback, leavingStateCallback);
} catch (final MissingEntryException e) {
throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), ""));
} catch (final OperationException e) {
if (e.getCause() instanceof PaymentApiException) {
throw (PaymentApiException) e.getCause();
// If the control plugin tries to pass us back a PaymentApiException we throw it
} else if (e.getCause() instanceof PaymentControlApiException && e.getCause().getCause() instanceof PaymentApiException) {
throw (PaymentApiException) e.getCause().getCause();
} else if (e.getCause() != null || paymentStateContext.getResult() == null) {
throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), ""));
}
}
// we don't throw, and return the failed Payment instead to be consistent with what happens when we don't go through control api.
return paymentStateContext.getResult();
}
use of org.killbill.billing.control.plugin.api.PaymentControlApiException in project killbill by killbill.
the class ControlPluginRunner method executePluginOnSuccessCalls.
public OnSuccessPaymentControlResult executePluginOnSuccessCalls(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) {
final PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account, paymentMethodId, paymentAttemptId, paymentId, paymentExternalKey, paymentTransactionId, paymentTransactionExternalKey, paymentApiType, transactionType, hppType, amount, currency, processedAmount, processedCurrency, isApiPayment, callContext);
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 OnSuccessPaymentControlResult result = plugin.onSuccessCall(inputPaymentControlContext, inputPluginProperties);
log.debug("Successful executed onSuccessCall of plugin {}", pluginName);
if (result == null) {
// Nothing returned by the plugin
continue;
}
if (result.getAdjustedPluginProperties() != null) {
inputPluginProperties = result.getAdjustedPluginProperties();
}
// Exceptions from the control plugins are ignored (and logged) because the semantics on what to do are undefined.
} catch (final PaymentControlApiException e) {
log.warn("Error during onSuccessCall for plugin='{}', paymentExternalKey='{}'", pluginName, inputPaymentControlContext.getPaymentExternalKey(), e);
} catch (final RuntimeException e) {
log.warn("Error during onSuccessCall for plugin='{}', paymentExternalKey='{}'", pluginName, inputPaymentControlContext.getPaymentExternalKey(), e);
}
}
}
return new DefaultOnSuccessPaymentControlResult(inputPluginProperties);
}
use of org.killbill.billing.control.plugin.api.PaymentControlApiException in project killbill by killbill.
the class TestPaymentApi method testCreateAbortedRefundWithPaymentControl.
@Test(groups = "slow")
public void testCreateAbortedRefundWithPaymentControl() throws PaymentApiException, InvoiceApiException, EventBusException {
final BigDecimal requestedAmount = BigDecimal.ONE;
final UUID subscriptionId = UUID.randomUUID();
final UUID bundleId = UUID.randomUUID();
final LocalDate now = clock.getUTCToday();
final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
final String paymentExternalKey = invoice.getId().toString();
final String transactionExternalKey = "payment";
final String transactionExternalKey2 = "refund";
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);
final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey, createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
final List<PluginProperty> refundProperties = ImmutableList.<PluginProperty>of();
try {
paymentApi.createRefundWithPaymentControl(account, payment.getId(), BigDecimal.TEN, Currency.USD, transactionExternalKey2, refundProperties, INVOICE_PAYMENT, callContext);
} catch (final PaymentApiException e) {
assertTrue(e.getCause() instanceof PaymentControlApiException);
}
}
use of org.killbill.billing.control.plugin.api.PaymentControlApiException in project killbill by killbill.
the class InvoicePaymentControlPluginApi method getPluginPurchaseResult.
private PriorPaymentControlResult getPluginPurchaseResult(final PaymentControlContext paymentControlPluginContext, final Iterable<PluginProperty> pluginProperties, final InternalCallContext internalContext) throws PaymentControlApiException {
try {
final UUID invoiceId = getInvoiceId(pluginProperties);
final Invoice invoice = getAndSanitizeInvoice(invoiceId, internalContext);
if (!InvoiceStatus.COMMITTED.equals(invoice.getStatus())) {
// abort payment if the invoice status is not COMMITTED
return new DefaultPriorPaymentControlResult(true);
}
// Get account and check if it is child and payment is delegated to parent => abort
final AccountData accountData = accountApi.getAccountById(invoice.getAccountId(), internalContext);
if ((accountData != null) && (accountData.getParentAccountId() != null) && accountData.isPaymentDelegatedToParent()) {
return new DefaultPriorPaymentControlResult(true);
}
final BigDecimal requestedAmount = validateAndComputePaymentAmount(invoice, paymentControlPluginContext.getAmount(), paymentControlPluginContext.isApiPayment());
final boolean isAborted = requestedAmount.compareTo(BigDecimal.ZERO) == 0;
if (!isAborted && paymentControlPluginContext.getPaymentMethodId() == null) {
log.warn("Payment for invoiceId='{}' was not triggered, accountId='{}' doesn't have a default payment method", getInvoiceId(pluginProperties), paymentControlPluginContext.getAccountId());
invoiceApi.recordPaymentAttemptCompletion(invoiceId, paymentControlPluginContext.getAmount(), paymentControlPluginContext.getCurrency(), paymentControlPluginContext.getProcessedCurrency(), paymentControlPluginContext.getPaymentId(), paymentControlPluginContext.getTransactionExternalKey(), paymentControlPluginContext.getCreatedDate(), false, internalContext);
return new DefaultPriorPaymentControlResult(true);
}
if (!isAborted && insert_AUTO_PAY_OFF_ifRequired(paymentControlPluginContext, requestedAmount)) {
return new DefaultPriorPaymentControlResult(true);
}
if (paymentControlPluginContext.isApiPayment() && isAborted) {
throw new PaymentControlApiException("Abort purchase call: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, String.format("Aborted Payment for invoice %s : invoice balance is = %s, requested payment amount is = %s", invoice.getId(), invoice.getBalance(), paymentControlPluginContext.getAmount())));
} else {
//
// Insert attempt row with a success = false status to implement a two-phase commit strategy and guard against scenario where payment would go through
// but onSuccessCall callback never gets called (leaving the place for a double payment if user retries the operation)
//
invoiceApi.recordPaymentAttemptInit(invoice.getId(), MoreObjects.firstNonNull(paymentControlPluginContext.getAmount(), BigDecimal.ZERO), paymentControlPluginContext.getCurrency(), paymentControlPluginContext.getCurrency(), // to match the operation in the checkForIncompleteInvoicePaymentAndRepair logic below
paymentControlPluginContext.getPaymentId(), paymentControlPluginContext.getTransactionExternalKey(), paymentControlPluginContext.getCreatedDate(), internalContext);
return new DefaultPriorPaymentControlResult(isAborted, requestedAmount);
}
} catch (final InvoiceApiException e) {
throw new PaymentControlApiException(e);
} catch (final IllegalArgumentException e) {
throw new PaymentControlApiException(e);
} catch (AccountApiException e) {
throw new PaymentControlApiException(e);
}
}
use of org.killbill.billing.control.plugin.api.PaymentControlApiException in project killbill by killbill.
the class InvoicePaymentControlPluginApi method computeRefundAmount.
private BigDecimal computeRefundAmount(final UUID paymentId, @Nullable final BigDecimal specifiedRefundAmount, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final InternalTenantContext context) throws PaymentControlApiException {
if (specifiedRefundAmount != null) {
if (specifiedRefundAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new PaymentControlApiException("Failed to compute refund: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, "You need to specify a positive refund amount"));
}
return specifiedRefundAmount;
}
try {
final List<InvoiceItem> items = invoiceApi.getInvoiceForPaymentId(paymentId, context).getInvoiceItems();
BigDecimal amountFromItems = BigDecimal.ZERO;
for (final UUID itemId : invoiceItemIdsWithAmounts.keySet()) {
final BigDecimal specifiedItemAmount = invoiceItemIdsWithAmounts.get(itemId);
final BigDecimal itemAmount = getAmountFromItem(items, itemId);
if (specifiedItemAmount != null && (specifiedItemAmount.compareTo(BigDecimal.ZERO) <= 0 || specifiedItemAmount.compareTo(itemAmount) > 0)) {
throw new PaymentControlApiException("Failed to compute refund: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, "You need to specify a valid invoice item amount"));
}
amountFromItems = amountFromItems.add(MoreObjects.firstNonNull(specifiedItemAmount, itemAmount));
}
return amountFromItems;
} catch (final InvoiceApiException e) {
throw new PaymentControlApiException(e);
}
}
Aggregations