use of org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult 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 ? Boolean.valueOf((String) prop.getValue()) : 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.retry.DefaultPriorPaymentControlResult in project killbill by killbill.
the class ControlPluginRunner method executePluginPriorCalls.
public PriorPaymentControlResult executePluginPriorCalls(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) throws PaymentControlApiException {
// Return as soon as the first plugin aborts, or the last result for the last plugin
PriorPaymentControlResult prevResult = new DefaultPriorPaymentControlResult(false, amount, currency, paymentMethodId, pluginProperties);
// Those values are adjusted prior each call with the result of what previous call to plugin returned
UUID inputPaymentMethodId = paymentMethodId;
BigDecimal inputAmount = amount;
Currency inputCurrency = currency;
Iterable<PluginProperty> inputPluginProperties = pluginProperties;
PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account, paymentMethodId, paymentAttemptId, paymentId, paymentExternalKey, paymentTransactionId, paymentTransactionExternalKey, paymentApiType, transactionType, hppType, amount, currency, processedAmount, processedCurrency, isApiPayment, callContext);
for (final String pluginName : paymentControlPluginNames) {
final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
if (plugin == null) {
// First call to plugin, we log warn, if plugin is not registered
log.warn("Skipping unknown payment control plugin {} when fetching results", pluginName);
continue;
}
log.debug("Calling priorCall of plugin {}", pluginName);
prevResult = plugin.priorCall(inputPaymentControlContext, inputPluginProperties);
log.debug("Successful executed priorCall of plugin {}", pluginName);
if (prevResult == null) {
// Nothing returned by the plugin
continue;
}
if (prevResult.getAdjustedPaymentMethodId() != null) {
inputPaymentMethodId = prevResult.getAdjustedPaymentMethodId();
}
if (prevResult.getAdjustedAmount() != null) {
inputAmount = prevResult.getAdjustedAmount();
}
if (prevResult.getAdjustedCurrency() != null) {
inputCurrency = prevResult.getAdjustedCurrency();
}
if (prevResult.getAdjustedPluginProperties() != null) {
inputPluginProperties = prevResult.getAdjustedPluginProperties();
}
if (prevResult.isAborted()) {
throw new PaymentControlApiAbortException(pluginName);
}
inputPaymentControlContext = new DefaultPaymentControlContext(account, inputPaymentMethodId, paymentAttemptId, paymentId, paymentExternalKey, paymentTransactionId, paymentTransactionExternalKey, paymentApiType, transactionType, hppType, inputAmount, inputCurrency, processedAmount, processedCurrency, isApiPayment, callContext);
}
// Rebuild latest result to include inputPluginProperties
prevResult = new DefaultPriorPaymentControlResult(prevResult != null && prevResult.isAborted(), inputPaymentMethodId, inputAmount, inputCurrency, inputPluginProperties);
return prevResult;
}
use of org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult 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.payment.retry.DefaultPriorPaymentControlResult in project killbill by killbill.
the class InvoicePaymentControlPluginApi method priorCall.
@Override
public PriorPaymentControlResult priorCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> pluginProperties) throws PaymentControlApiException {
final TransactionType transactionType = paymentControlContext.getTransactionType();
Preconditions.checkArgument(paymentControlContext.getPaymentApiType() == PaymentApiType.PAYMENT_TRANSACTION);
Preconditions.checkArgument(transactionType == TransactionType.PURCHASE || transactionType == TransactionType.REFUND || transactionType == TransactionType.CHARGEBACK || transactionType == TransactionType.CREDIT);
final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext);
switch(transactionType) {
case PURCHASE:
return getPluginPurchaseResult(paymentControlContext, pluginProperties, internalContext);
case REFUND:
return getPluginRefundResult(paymentControlContext, pluginProperties, internalContext);
case CHARGEBACK:
return new DefaultPriorPaymentControlResult(false, paymentControlContext.getAmount());
case CREDIT:
return getPluginCreditResult(paymentControlContext, pluginProperties, internalContext);
default:
throw new IllegalStateException("Unexpected transactionType " + transactionType);
}
}
Aggregations