killbill-aplcache

jaxrs: Add endpoint to complete an payment associated with

10/6/2017 10:07:42 PM

Details

diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java
index 17ce949..bc87d1e 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java
@@ -123,13 +123,4 @@ public abstract class ComboPaymentResource extends JaxRsResourceBase {
                                            paymentData.getPluginDetail(), pluginProperties, callContext);
     }
 
-    protected Payment getPaymentByIdOrKey(@Nullable final String paymentIdStr, @Nullable final String externalKey, final Iterable<PluginProperty> pluginProperties, final TenantContext tenantContext) throws PaymentApiException {
-        Preconditions.checkArgument(paymentIdStr != null || externalKey != null, "Need to set either paymentId or payment externalKey");
-        if (paymentIdStr != null) {
-            final UUID paymentId = UUID.fromString(paymentIdStr);
-            return paymentApi.getPayment(paymentId, false, false, pluginProperties, tenantContext);
-        } else {
-            return paymentApi.getPaymentByExternalKey(externalKey, false, false, pluginProperties, tenantContext);
-        }
-    }
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
index 440d83a..45e0cb4 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
@@ -19,6 +19,7 @@
 package org.killbill.billing.jaxrs.resources;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -33,11 +34,13 @@ import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
 import javax.ws.rs.HeaderParam;
 import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
 
 import org.killbill.billing.ObjectType;
@@ -51,6 +54,7 @@ import org.killbill.billing.jaxrs.json.CustomFieldJson;
 import org.killbill.billing.jaxrs.json.InvoiceItemJson;
 import org.killbill.billing.jaxrs.json.InvoicePaymentJson;
 import org.killbill.billing.jaxrs.json.InvoicePaymentTransactionJson;
+import org.killbill.billing.jaxrs.json.PaymentTransactionJson;
 import org.killbill.billing.jaxrs.json.TagJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
@@ -69,6 +73,7 @@ import org.killbill.billing.util.audit.AccountAuditLogs;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.clock.Clock;
+import org.killbill.commons.metrics.MetricTag;
 import org.killbill.commons.metrics.TimedResource;
 
 import com.google.common.base.Predicate;
@@ -258,6 +263,62 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
         return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId(), request);
     }
 
+
+    @TimedResource(name = "completeInvoicePaymentTransaction")
+    @PUT
+    @Path("/{paymentId:" + UUID_PATTERN + "}")
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Complete an existing transaction")
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+                           @ApiResponse(code = 400, message = "Invalid paymentId supplied"),
+                           @ApiResponse(code = 404, message = "Account or payment not found"),
+                           @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+                           @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+                           @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+                           @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+                           @ApiResponse(code = 504, message = "Payment operation timeout")})
+    public Response completeInvoicePaymentTransaction(final PaymentTransactionJson json,
+                                        @PathParam("paymentId") final String paymentIdStr,
+                                        @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
+                                        @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                        @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                        @HeaderParam(HDR_REASON) final String reason,
+                                        @HeaderParam(HDR_COMMENT) final String comment,
+                                        @javax.ws.rs.core.Context final UriInfo uriInfo,
+                                        @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+
+        final TenantContext tenantContext = context.createTenantContextNoAccountId(request);
+
+        final UUID paymentId = UUID.fromString(paymentIdStr);
+
+        final Payment payment = paymentApi.getPayment(paymentId, false, false, ImmutableList.<PluginProperty>of(), tenantContext);
+        final List<InvoicePayment> invoicePayments = invoicePaymentApi.getInvoicePayments(paymentId, tenantContext);
+
+        final InvoicePayment originalInvoicePaymentAttempt = Iterables.tryFind(invoicePayments, new Predicate<InvoicePayment>() {
+            @Override
+            public boolean apply(final InvoicePayment input) {
+                return input.getType() == InvoicePaymentType.ATTEMPT && !input.isSuccess();
+            }
+        }).orNull();
+
+        final UUID invoiceId = originalInvoicePaymentAttempt != null ? originalInvoicePaymentAttempt.getInvoiceId() : null;
+        if (invoiceId == null) {
+            return Response.status(Status.NOT_FOUND).build();
+        }
+
+        final PluginProperty invoiceProperty = new PluginProperty("IPCD_INVOICE_ID" /* InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID (contract with plugin)  */,
+                                                                  invoiceId.toString(), false);
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString, invoiceProperty);
+
+        final List<String> controlPluginNames = new ArrayList<String>();
+        controlPluginNames.add("__INVOICE_PAYMENT_CONTROL_PLUGIN__");
+        controlPluginNames.addAll(paymentControlPluginNames);
+
+        return completeTransactionInternal(json, payment, controlPluginNames, pluginProperties, tenantContext, createdBy, reason, comment, uriInfo, request);
+    }
+
+
     @TimedResource
     @GET
     @Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
index 97c9b8c..07fbc86 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -52,6 +52,7 @@ import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.entitlement.api.EntitlementApiException;
@@ -64,6 +65,7 @@ import org.killbill.billing.jaxrs.json.BillingExceptionJson.StackTraceElementJso
 import org.killbill.billing.jaxrs.json.BlockingStateJson;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
 import org.killbill.billing.jaxrs.json.JsonBase;
+import org.killbill.billing.jaxrs.json.PaymentTransactionJson;
 import org.killbill.billing.jaxrs.json.PluginPropertyJson;
 import org.killbill.billing.jaxrs.json.TagJson;
 import org.killbill.billing.jaxrs.util.Context;
@@ -119,7 +121,6 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
     // Catalog API don't quite support multiple catalogs per tenant
     protected static final String catalogName = "unused";
 
-
     protected static final ObjectMapper mapper = new ObjectMapper();
 
     protected final JaxrsUriBuilder uriBuilder;
@@ -183,9 +184,6 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
         return Response.status(Status.OK).build();
     }
 
-
-
-
     protected Response getTags(final UUID accountId, final UUID taggedObjectId, final AuditMode auditMode, final boolean includeDeleted, final TenantContext context) throws TagDefinitionApiException {
         final List<Tag> tags = tagUserApi.getTagsForObject(taggedObjectId, getObjectType(), includeDeleted, context);
         return createTagResponse(accountId, tags, auditMode, context);
@@ -208,7 +206,6 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
         return Response.status(Response.Status.OK).entity(result).build();
     }
 
-
     protected Response createTags(final UUID id,
                                   final String tagList,
                                   final UriInfo uriInfo,
@@ -361,8 +358,80 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
         }
     }
 
+    protected Payment getPaymentByIdOrKey(@Nullable final String paymentIdStr, @Nullable final String externalKey, final Iterable<PluginProperty> pluginProperties, final TenantContext tenantContext) throws PaymentApiException {
+        Preconditions.checkArgument(paymentIdStr != null || externalKey != null, "Need to set either paymentId or payment externalKey");
+        if (paymentIdStr != null) {
+            final UUID paymentId = UUID.fromString(paymentIdStr);
+            return paymentApi.getPayment(paymentId, false, false, pluginProperties, tenantContext);
+        } else {
+            return paymentApi.getPaymentByExternalKey(externalKey, false, false, pluginProperties, tenantContext);
+        }
+    }
+
+
+    protected Response completeTransactionInternal(final PaymentTransactionJson json,
+                                                   final Payment initialPayment,
+                                                   final List<String> paymentControlPluginNames,
+                                                   final Iterable<PluginProperty> pluginProperties,
+                                                   final TenantContext contextNoAccountId,
+                                                   final String createdBy,
+                                                   final String reason,
+                                                   final String comment,
+                                                   final UriInfo uriInfo,
+                                                   final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+
+        final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), contextNoAccountId);
+        final BigDecimal amount = json == null ? null : json.getAmount();
+        final Currency currency = json == null || json.getCurrency() == null ? null : Currency.valueOf(json.getCurrency());
+
+        final CallContext callContext = context.createCallContextWithAccountId(account.getId(), createdBy, reason, comment, request);
+
+        final PaymentTransaction pendingOrSuccessTransaction = lookupPendingOrSuccessTransaction(initialPayment,
+                                                                                                 json != null ? json.getTransactionId() : null,
+                                                                                                 json != null ? json.getTransactionExternalKey() : null,
+                                                                                                 json != null ? json.getTransactionType() : null);
+        // If transaction was already completed, return early (See #626)
+        if (pendingOrSuccessTransaction.getTransactionStatus() == TransactionStatus.SUCCESS) {
+            return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", pendingOrSuccessTransaction.getPaymentId(), request);
+        }
+
+        final PaymentTransaction pendingTransaction = pendingOrSuccessTransaction;
+        final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
+        final Payment result;
+        switch (pendingTransaction.getTransactionType()) {
+            case AUTHORIZE:
+                result = paymentApi.createAuthorizationWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
+                                                                          initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
+                                                                          pluginProperties, paymentOptions, callContext);
+                break;
+            case CAPTURE:
+                result = paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), amount, currency, pendingTransaction.getExternalKey(),
+                                                                    pluginProperties, paymentOptions, callContext);
+                break;
+            case PURCHASE:
+                result = paymentApi.createPurchaseWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
+                                                                     initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
+                                                                     pluginProperties, paymentOptions, callContext);
+                break;
+            case CREDIT:
+                result = paymentApi.createCreditWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
+                                                                   initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
+                                                                   pluginProperties, paymentOptions, callContext);
+                break;
+            case REFUND:
+                result = paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), amount, currency,
+                                                                   pendingTransaction.getExternalKey(), pluginProperties, paymentOptions, callContext);
+                break;
+            default:
+                return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + pendingTransaction.getTransactionType() + " cannot be completed").build();
+        }
+        return createPaymentResponse(uriInfo, result, pendingTransaction.getTransactionType(), pendingTransaction.getExternalKey(), request);
+
+    }
+
+
     protected PaymentTransaction lookupPendingOrSuccessTransaction(final Payment initialPayment, @Nullable final String transactionId, @Nullable final String transactionExternalKey, @Nullable final String transactionType) throws PaymentApiException {
-        final Collection<PaymentTransaction> pendingTransaction  =  Collections2.filter(initialPayment.getTransactions(), new Predicate<PaymentTransaction>() {
+        final Collection<PaymentTransaction> pendingTransaction = Collections2.filter(initialPayment.getTransactions(), new Predicate<PaymentTransaction>() {
             @Override
             public boolean apply(final PaymentTransaction input) {
                 if (input.getTransactionStatus() != TransactionStatus.PENDING && input.getTransactionStatus() != TransactionStatus.SUCCESS) {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
index c5c98e8..bf63aaa 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
@@ -17,7 +17,6 @@
 
 package org.killbill.billing.jaxrs.resources;
 
-import java.math.BigDecimal;
 import java.net.URI;
 import java.util.HashMap;
 import java.util.List;
@@ -59,9 +58,7 @@ import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PaymentOptions;
-import org.killbill.billing.payment.api.PaymentTransaction;
 import org.killbill.billing.payment.api.PluginProperty;
-import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.api.CustomFieldApiException;
@@ -263,9 +260,11 @@ public class PaymentResource extends ComboPaymentResource {
                                         @HeaderParam(HDR_COMMENT) final String comment,
                                         @javax.ws.rs.core.Context final UriInfo uriInfo,
                                         @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
-        return completeTransactionInternal(json, paymentIdStr, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
+        return completeTransactionInternalWithoutPayment(json, paymentIdStr, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
     }
 
+
+
     @TimedResource(name = "completeTransaction")
     @PUT
     @Consumes(APPLICATION_JSON)
@@ -286,7 +285,7 @@ public class PaymentResource extends ComboPaymentResource {
                                                      @HeaderParam(HDR_COMMENT) final String comment,
                                                      @javax.ws.rs.core.Context final UriInfo uriInfo,
                                                      @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
-        return completeTransactionInternal(json, null, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
+        return completeTransactionInternalWithoutPayment(json, null, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
     }
 
 
@@ -294,71 +293,6 @@ public class PaymentResource extends ComboPaymentResource {
 
 
 
-    private Response completeTransactionInternal(final PaymentTransactionJson json,
-                                                 @Nullable final String paymentIdStr,
-                                                 final List<String> paymentControlPluginNames,
-                                                 final Iterable<String> pluginPropertiesString,
-                                                 final String createdBy,
-                                                 final String reason,
-                                                 final String comment,
-                                                 final UriInfo uriInfo,
-                                                 final HttpServletRequest request) throws PaymentApiException, AccountApiException {
-
-        final Iterable<PluginProperty> pluginPropertiesFromBody = extractPluginProperties(json.getProperties());
-
-        final Iterable<PluginProperty> pluginPropertiesFromQuery = extractPluginProperties(pluginPropertiesString);
-
-        final Iterable<PluginProperty> pluginProperties = Iterables.concat(pluginPropertiesFromQuery, pluginPropertiesFromBody);
-
-        final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
-        final Payment initialPayment = getPaymentByIdOrKey(paymentIdStr, json == null ? null : json.getPaymentExternalKey(), pluginProperties, callContext);
-
-        final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext);
-        final BigDecimal amount = json == null ? null : json.getAmount();
-        final Currency currency = json == null || json.getCurrency() == null ? null : Currency.valueOf(json.getCurrency());
-
-        final PaymentTransaction pendingOrSuccessTransaction = lookupPendingOrSuccessTransaction(initialPayment,
-                                                                                                 json != null ? json.getTransactionId() : null,
-                                                                                                 json != null ? json.getTransactionExternalKey() : null,
-                                                                                                 json != null ? json.getTransactionType() : null);
-        // If transaction was already completed, return early (See #626)
-        if (pendingOrSuccessTransaction.getTransactionStatus() == TransactionStatus.SUCCESS) {
-            return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", pendingOrSuccessTransaction.getPaymentId(), request);
-        }
-
-
-        final PaymentTransaction pendingTransaction = pendingOrSuccessTransaction;
-        final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
-        final Payment result;
-        switch (pendingTransaction.getTransactionType()) {
-            case AUTHORIZE:
-                result = paymentApi.createAuthorizationWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
-                                                                 initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
-                                                                 pluginProperties, paymentOptions, callContext);
-                break;
-            case CAPTURE:
-                result = paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), amount, currency, pendingTransaction.getExternalKey(),
-                                                           pluginProperties, paymentOptions, callContext);
-                break;
-            case PURCHASE:
-                result = paymentApi.createPurchaseWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
-                                                            initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
-                                                            pluginProperties, paymentOptions, callContext);
-                break;
-            case CREDIT:
-                result = paymentApi.createCreditWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
-                                                          initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
-                                                          pluginProperties, paymentOptions, callContext);
-                break;
-            case REFUND:
-                result = paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), amount, currency,
-                                                          pendingTransaction.getExternalKey(), pluginProperties, paymentOptions, callContext);
-                break;
-            default:
-                return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + pendingTransaction.getTransactionType() + " cannot be completed").build();
-        }
-        return createPaymentResponse(uriInfo, result, pendingTransaction.getTransactionType(), pendingTransaction.getExternalKey(), request);
-    }
 
     @TimedResource(name = "captureAuthorization")
     @POST
@@ -950,4 +884,27 @@ public class PaymentResource extends ComboPaymentResource {
     protected ObjectType getObjectType() {
         return ObjectType.PAYMENT;
     }
+
+    private Response completeTransactionInternalWithoutPayment(final PaymentTransactionJson json,
+                                                               @Nullable final String paymentIdStr,
+                                                               final List<String> paymentControlPluginNames,
+                                                               final Iterable<String> pluginPropertiesString,
+                                                               final String createdBy,
+                                                               final String reason,
+                                                               final String comment,
+                                                               final UriInfo uriInfo,
+                                                               final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+
+        final Iterable<PluginProperty> pluginPropertiesFromBody = extractPluginProperties(json.getProperties());
+
+        final Iterable<PluginProperty> pluginPropertiesFromQuery = extractPluginProperties(pluginPropertiesString);
+
+        final Iterable<PluginProperty> pluginProperties = Iterables.concat(pluginPropertiesFromQuery, pluginPropertiesFromBody);
+
+        final CallContext callContextNoAccountId = context.createCallContextNoAccountId(createdBy, reason, comment, request);
+        final Payment initialPayment = getPaymentByIdOrKey(paymentIdStr, json == null ? null : json.getPaymentExternalKey(), pluginProperties, callContextNoAccountId);
+
+        return completeTransactionInternal(json, initialPayment, paymentControlPluginNames, pluginProperties, callContextNoAccountId, createdBy, reason, comment, uriInfo, request);
+    }
+
 }
diff --git a/profiles/killbill/src/main/resources/killbill-server.properties b/profiles/killbill/src/main/resources/killbill-server.properties
index 6208aa4..213957d 100644
--- a/profiles/killbill/src/main/resources/killbill-server.properties
+++ b/profiles/killbill/src/main/resources/killbill-server.properties
@@ -23,7 +23,7 @@
 # KILLBILL GENERIC PROPERTIES
 #
 # Database config
-org.killbill.dao.url=jdbc:mysql://127.0.0.1:3306/killbill
+org.killbill.dao.url=jdbc:mysql://127.0.0.1:3306/killbill_0_19_x
 org.killbill.dao.user=root
 org.killbill.dao.password=root
 org.killbill.dao.logLevel=DEBUG
@@ -62,7 +62,7 @@ org.killbill.tenant.broadcast.rate=1s
 # PLUGIN SPECIFIC PROPERTIES
 #
 # Database config (OSGI plugins)
-org.killbill.billing.osgi.dao.url=jdbc:mysql://127.0.0.1:3306/killbill
+org.killbill.billing.osgi.dao.url=jdbc:mysql://127.0.0.1:3306/killbill_0_19_x
 org.killbill.billing.osgi.dao.user=root
 org.killbill.billing.osgi.dao.password=root
 
@@ -88,3 +88,5 @@ org.killbill.payment.retry.days=1,1,1
 
 org.killbill.notificationq.analytics.tableName=analytics_notifications
 org.killbill.notificationq.analytics.historyTableName=analytics_notifications_history
+
+org.killbill.osgi.bundle.install.dir=/Users/sbrossier/Documents/KillBillConfig/bundles_0_19_x/
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java
index e7c5755..9803ca8 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java
@@ -45,6 +45,7 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.inject.Inject;
 
 import static org.testng.Assert.assertEquals;
@@ -70,6 +71,32 @@ public class TestInvoicePayment extends TestJaxrsBase {
         Assert.assertTrue(retrievedPaymentJson.equals((Payment) paymentJson));
     }
 
+
+    @Test(groups = "slow")
+    public void testInvoicePaymentCompletion() throws Exception {
+        mockPaymentProviderPlugin.makeNextPaymentPending();
+
+        final InvoicePayment paymentJson = setupScenarioWithPayment();
+
+        final Payment retrievedPaymentJson = killBillClient.getPayment(paymentJson.getPaymentId(), false, requestOptions);
+        Assert.assertTrue(retrievedPaymentJson.equals((Payment) paymentJson));
+        Assert.assertEquals(retrievedPaymentJson.getTransactions().size(), 1);
+        Assert.assertEquals(retrievedPaymentJson.getTransactions().get(0).getStatus(), "PENDING");
+
+        final PaymentTransaction completeTransactionByPaymentId = new PaymentTransaction();
+        completeTransactionByPaymentId.setPaymentId(retrievedPaymentJson.getPaymentId());
+
+        final Account accountWithBalance = killBillClient.getAccount(paymentJson.getAccountId(), true, false, requestOptions);
+        Assert.assertTrue(accountWithBalance.getAccountBalance().compareTo(BigDecimal.ZERO) > 0);
+
+        final Payment completedPayment = killBillClient.completeInvoicePayment(completeTransactionByPaymentId, null, ImmutableMap.<String, String>of(), requestOptions);
+        Assert.assertEquals(completedPayment.getTransactions().get(0).getStatus(), "SUCCESS");
+
+        final Account accountWithBalance2 = killBillClient.getAccount(paymentJson.getAccountId(), true, false, requestOptions);
+        Assert.assertEquals(accountWithBalance2.getAccountBalance().compareTo(BigDecimal.ZERO), 0);
+
+    }
+
     @Test(groups = "slow", description = "Can create a full refund with no adjustment")
     public void testFullRefundWithNoAdjustment() throws Exception {
         final InvoicePayment invoicePaymentJson = setupScenarioWithPayment();