use of org.killbill.billing.payment.api.PaymentApiException in project killbill by killbill.
the class TestPaymentRefund method testFailedRefundWithInvoiceAdjustment.
@Test(groups = "slow")
public void testFailedRefundWithInvoiceAdjustment() throws Exception {
final List<PluginProperty> properties = new ArrayList<PluginProperty>();
final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_REFUND_WITH_ADJUSTMENTS, "true", false);
properties.add(prop1);
try {
paymentApi.createRefundWithPaymentControl(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(), UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, callContext);
fail("Refund with invoice adjustment should now throw an Exception");
} catch (final PaymentApiException e) {
Assert.assertEquals(e.getCause(), null);
// Unfortunately we lose the original error code : INVOICE_ITEMS_ADJUSTMENT_MISSING
Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_PLUGIN_EXCEPTION.getCode());
}
}
use of org.killbill.billing.payment.api.PaymentApiException in project killbill by killbill.
the class PluginControlPaymentAutomatonRunner method run.
public Payment run(final State state, final boolean isApiPayment, final Boolean isSuccess, final TransactionType transactionType, final ControlOperation controlOperation, final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, @Nullable final String paymentExternalKey, @Nullable final UUID transactionId, final String paymentTransactionExternalKey, @Nullable final BigDecimal amount, @Nullable final Currency currency, final Iterable<PluginProperty> properties, @Nullable final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
final PaymentStateControlContext paymentStateContext = createContext(isApiPayment, isSuccess, transactionType, account, paymentMethodId, paymentId, paymentExternalKey, transactionId, paymentTransactionExternalKey, amount, currency, properties, paymentControlPluginNames, callContext, internalCallContext);
try {
final OperationCallback callback = createOperationCallback(controlOperation, paymentStateContext);
final LeavingStateCallback leavingStateCallback = new DefaultControlInitiated(this, paymentStateContext, paymentDao, paymentControlStateMachineHelper.getInitialState(), paymentControlStateMachineHelper.getRetriedState(), transactionType);
final EnteringStateCallback enteringStateCallback = new DefaultControlCompleted(this, paymentStateContext, paymentControlStateMachineHelper.getRetriedState(), retryServiceScheduler);
state.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.payment.api.PaymentApiException in project killbill by killbill.
the class DefaultStateMachineConfigCache method getPaymentStateMachineConfig.
@Override
public StateMachineConfig getPaymentStateMachineConfig(final String pluginName, final InternalTenantContext tenantContext) throws PaymentApiException {
if (InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID.equals(tenantContext.getTenantRecordId()) || cacheController == null) {
return defaultPaymentStateMachineConfig;
}
final String pluginConfigKey = getCacheKeyName(pluginName, tenantContext);
final CacheLoaderArgument cacheLoaderArgument = createCacheLoaderArgument(pluginName);
try {
StateMachineConfig pluginPaymentStateMachineConfig = 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.putIfAbsent(pluginConfigKey, pluginPaymentStateMachineConfig);
}
((DefaultStateMachineConfig) pluginPaymentStateMachineConfig).initialize(defaultPaymentStateMachineConfig);
return pluginPaymentStateMachineConfig;
} catch (final IllegalStateException e) {
// TODO 0.17 proper error code
throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, "Invalid payment state machine");
}
}
use of org.killbill.billing.payment.api.PaymentApiException in project killbill by killbill.
the class InvoicePaymentControlPluginApi method getPluginRefundResult.
private PriorPaymentControlResult getPluginRefundResult(final PaymentControlContext paymentControlPluginContext, final Iterable<PluginProperty> pluginProperties, final InternalCallContext internalContext) throws PaymentControlApiException {
final Map<UUID, BigDecimal> idWithAmount = extractIdsWithAmountFromProperties(pluginProperties);
if ((paymentControlPluginContext.getAmount() == null || paymentControlPluginContext.getAmount().compareTo(BigDecimal.ZERO) == 0) && idWithAmount.size() == 0) {
throw new PaymentControlApiException("Abort refund call: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, String.format("Refund for payment, key = %s, aborted: requested refund amount is = %s", paymentControlPluginContext.getPaymentExternalKey(), paymentControlPluginContext.getAmount())));
}
final PaymentModelDao payment = paymentDao.getPayment(paymentControlPluginContext.getPaymentId(), internalContext);
if (payment == null) {
throw new PaymentControlApiException("Unexpected null payment");
}
// This will calculate the upper bound on the refund amount based on the invoice items associated with that payment.
// Note that we are not checking that other (partial) refund occurred, but if the refund ends up being greater than what is allowed
// the call to the gateway would fail; it would need noce to validate on our side though...
final BigDecimal amountToBeRefunded = computeRefundAmount(payment.getId(), paymentControlPluginContext.getAmount(), idWithAmount, internalContext);
final boolean isAborted = amountToBeRefunded.compareTo(BigDecimal.ZERO) == 0;
if (paymentControlPluginContext.isApiPayment() && isAborted) {
throw new PaymentControlApiException("Abort refund call: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, String.format("Refund for payment %s aborted : invoice item sum amount is %s, requested refund amount is = %s", payment.getId(), amountToBeRefunded, paymentControlPluginContext.getAmount())));
}
final PluginProperty prop = getPluginProperty(pluginProperties, PROP_IPCD_REFUND_WITH_ADJUSTMENTS);
final boolean isAdjusted = prop != null && prop.getValue() != null ? Boolean.valueOf(prop.getValue().toString()) : false;
if (isAdjusted) {
try {
invoiceApi.validateInvoiceItemAdjustments(paymentControlPluginContext.getPaymentId(), idWithAmount, internalContext);
} catch (InvoiceApiException e) {
throw new PaymentControlApiException(String.format("Refund for payment %s aborted", payment.getId()), new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getMessage()));
}
}
return new DefaultPriorPaymentControlResult(isAborted, amountToBeRefunded);
}
use of org.killbill.billing.payment.api.PaymentApiException in project killbill by killbill.
the class TestPaymentProcessor method testInvalidTransition.
@Test(groups = "slow")
public void testInvalidTransition() throws Exception {
final String paymentExternalKey = UUID.randomUUID().toString();
final Iterable<PluginProperty> pluginPropertiesToDriveTransationToPending = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.ERROR, false));
// AUTH
final String authorizationKey = UUID.randomUUID().toString();
final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, null, paymentExternalKey, authorizationKey, null, null, SHOULD_LOCK_ACCOUNT, pluginPropertiesToDriveTransationToPending, callContext, internalCallContext);
verifyPayment(authorization, paymentExternalKey, ZERO, ZERO, ZERO, 1);
final UUID paymentId = authorization.getId();
verifyPaymentTransaction(authorization.getTransactions().get(0), authorizationKey, TransactionType.AUTHORIZE, TEN, paymentId);
paymentBusListener.verify(0, 1, 0, account.getId(), paymentId, ZERO, TransactionStatus.PAYMENT_FAILURE);
// REFUND
final String refundKey = UUID.randomUUID().toString();
try {
paymentProcessor.createRefund(true, null, account, paymentId, TEN, CURRENCY, null, refundKey, null, SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
Assert.fail();
} catch (final PaymentApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
}
final Payment refreshedPayment = paymentRefresher.getPayment(authorization.getId(), false, false, true, PLUGIN_PROPERTIES, callContext, internalCallContext);
// Make sure no state has been created (no UNKNOWN transaction for the refund)
verifyPayment(refreshedPayment, paymentExternalKey, ZERO, ZERO, ZERO, 1);
paymentBusListener.verify(0, 1, 0, account.getId(), paymentId, ZERO, TransactionStatus.PAYMENT_FAILURE);
}
Aggregations