killbill-uncached

jaxrs: add support for HPP combo call Signed-off-by: Pierre-Alexandre

8/11/2015 9:03:05 AM

Details

diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboHostedPaymentPageJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboHostedPaymentPageJson.java
new file mode 100644
index 0000000..8e72c5f
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboHostedPaymentPageJson.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2015 Groupon, Inc
+ * Copyright 2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class ComboHostedPaymentPageJson extends ComboPaymentJson {
+
+    private final HostedPaymentPageFieldsJson hostedPaymentPageFields;
+
+    @JsonCreator
+    public ComboHostedPaymentPageJson(@JsonProperty("account") final AccountJson account,
+                                      @JsonProperty("paymentMethod") final PaymentMethodJson paymentMethod,
+                                      @JsonProperty("hostedPaymentPageFields") final HostedPaymentPageFieldsJson hostedPaymentPageFields,
+                                      @JsonProperty("paymentMethodPluginProperties") final Iterable<PluginPropertyJson> paymentMethodPluginProperties,
+                                      @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
+        super(account, paymentMethod, paymentMethodPluginProperties, auditLogs);
+        this.hostedPaymentPageFields = hostedPaymentPageFields;
+    }
+
+    public HostedPaymentPageFieldsJson getHostedPaymentPageFieldsJson() {
+        return hostedPaymentPageFields;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("ComboHostedPaymentPageJson{");
+        sb.append("hostedPaymentPageFields=").append(hostedPaymentPageFields);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        final ComboHostedPaymentPageJson that = (ComboHostedPaymentPageJson) o;
+
+        return !(hostedPaymentPageFields != null ? !hostedPaymentPageFields.equals(that.hostedPaymentPageFields) : that.hostedPaymentPageFields != null);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + (hostedPaymentPageFields != null ? hostedPaymentPageFields.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentJson.java
new file mode 100644
index 0000000..be99eca
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentJson.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2015 Groupon, Inc
+ * Copyright 2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public abstract class ComboPaymentJson extends JsonBase {
+
+    private final AccountJson account;
+    private final PaymentMethodJson paymentMethod;
+    private final Iterable<PluginPropertyJson> paymentMethodPluginProperties;
+
+    @JsonCreator
+    public ComboPaymentJson(@JsonProperty("account") final AccountJson account,
+                            @JsonProperty("paymentMethod") final PaymentMethodJson paymentMethod,
+                            @JsonProperty("paymentMethodPluginProperties") final Iterable<PluginPropertyJson> paymentMethodPluginProperties,
+                            @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
+        super(auditLogs);
+        this.account = account;
+        this.paymentMethod = paymentMethod;
+        this.paymentMethodPluginProperties = paymentMethodPluginProperties;
+    }
+
+    public AccountJson getAccount() {
+        return account;
+    }
+
+    public PaymentMethodJson getPaymentMethod() {
+        return paymentMethod;
+    }
+
+    public Iterable<PluginPropertyJson> getPaymentMethodPluginProperties() {
+        return paymentMethodPluginProperties;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("ComboPaymentJson{");
+        sb.append("account=").append(account);
+        sb.append(", paymentMethod=").append(paymentMethod);
+        sb.append(", paymentMethodPluginProperties=").append(paymentMethodPluginProperties);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final ComboPaymentJson that = (ComboPaymentJson) o;
+
+        if (account != null ? !account.equals(that.account) : that.account != null) {
+            return false;
+        }
+        if (paymentMethod != null ? !paymentMethod.equals(that.paymentMethod) : that.paymentMethod != null) {
+            return false;
+        }
+        return !(paymentMethodPluginProperties != null ? !paymentMethodPluginProperties.equals(that.paymentMethodPluginProperties) : that.paymentMethodPluginProperties != null);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = account != null ? account.hashCode() : 0;
+        result = 31 * result + (paymentMethod != null ? paymentMethod.hashCode() : 0);
+        result = 31 * result + (paymentMethodPluginProperties != null ? paymentMethodPluginProperties.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentTransactionJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentTransactionJson.java
index 9a50741..3ca1ead 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentTransactionJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ComboPaymentTransactionJson.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2015 Groupon, Inc
+ * Copyright 2015 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -24,12 +24,9 @@ import javax.annotation.Nullable;
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-public class ComboPaymentTransactionJson extends JsonBase {
+public class ComboPaymentTransactionJson extends ComboPaymentJson {
 
-    private final AccountJson account;
-    private final PaymentMethodJson paymentMethod;
     private final PaymentTransactionJson transaction;
-    private final Iterable<PluginPropertyJson> paymentMethodPluginProperties;
     private final Iterable<PluginPropertyJson> transactionPluginProperties;
 
     @JsonCreator
@@ -39,44 +36,26 @@ public class ComboPaymentTransactionJson extends JsonBase {
                                        @JsonProperty("paymentMethodPluginProperties") final Iterable<PluginPropertyJson> paymentMethodPluginProperties,
                                        @JsonProperty("transactionPluginProperties") final Iterable<PluginPropertyJson> transactionPluginProperties,
                                        @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
-        super(auditLogs);
-        this.account = account;
-        this.paymentMethod = paymentMethod;
+        super(account, paymentMethod, paymentMethodPluginProperties, auditLogs);
         this.transaction = transaction;
-        this.paymentMethodPluginProperties = paymentMethodPluginProperties;
         this.transactionPluginProperties = transactionPluginProperties;
     }
 
-
-    public AccountJson getAccount() {
-        return account;
-    }
-
-    public PaymentMethodJson getPaymentMethod() {
-        return paymentMethod;
-    }
-
     public PaymentTransactionJson getTransaction() {
         return transaction;
     }
 
-    public Iterable<PluginPropertyJson> getPaymentMethodPluginProperties() {
-        return paymentMethodPluginProperties;
-    }
-
     public Iterable<PluginPropertyJson> getTransactionPluginProperties() {
         return transactionPluginProperties;
     }
 
     @Override
     public String toString() {
-        return "ComboPaymentTransactionJson{" +
-               "account=" + account +
-               ", paymentMethod=" + paymentMethod +
-               ", transaction=" + transaction +
-               ", paymentMethodPluginProperties=" + paymentMethodPluginProperties +
-               ", transactionPluginProperties=" + transactionPluginProperties +
-               '}';
+        final StringBuilder sb = new StringBuilder("ComboPaymentTransactionJson{");
+        sb.append("transaction=").append(transaction);
+        sb.append(", transactionPluginProperties=").append(transactionPluginProperties);
+        sb.append('}');
+        return sb.toString();
     }
 
     @Override
@@ -84,34 +63,25 @@ public class ComboPaymentTransactionJson extends JsonBase {
         if (this == o) {
             return true;
         }
-        if (!(o instanceof ComboPaymentTransactionJson)) {
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        if (!super.equals(o)) {
             return false;
         }
 
         final ComboPaymentTransactionJson that = (ComboPaymentTransactionJson) o;
 
-        if (account != null ? !account.equals(that.account) : that.account != null) {
-            return false;
-        }
-        if (paymentMethod != null ? !paymentMethod.equals(that.paymentMethod) : that.paymentMethod != null) {
-            return false;
-        }
         if (transaction != null ? !transaction.equals(that.transaction) : that.transaction != null) {
             return false;
         }
-        if (paymentMethodPluginProperties != null ? !paymentMethodPluginProperties.equals(that.paymentMethodPluginProperties) : that.paymentMethodPluginProperties != null) {
-            return false;
-        }
         return !(transactionPluginProperties != null ? !transactionPluginProperties.equals(that.transactionPluginProperties) : that.transactionPluginProperties != null);
-
     }
 
     @Override
     public int hashCode() {
-        int result = account != null ? account.hashCode() : 0;
-        result = 31 * result + (paymentMethod != null ? paymentMethod.hashCode() : 0);
+        int result = super.hashCode();
         result = 31 * result + (transaction != null ? transaction.hashCode() : 0);
-        result = 31 * result + (paymentMethodPluginProperties != null ? paymentMethodPluginProperties.hashCode() : 0);
         result = 31 * result + (transactionPluginProperties != null ? transactionPluginProperties.hashCode() : 0);
         return result;
     }
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
new file mode 100644
index 0000000..a372150
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2015 Groupon, Inc
+ * Copyright 2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.resources;
+
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+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.jaxrs.json.AccountJson;
+import org.killbill.billing.jaxrs.json.PaymentMethodJson;
+import org.killbill.billing.jaxrs.util.Context;
+import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
+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.PaymentMethod;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.util.api.AuditUserApi;
+import org.killbill.billing.util.api.CustomFieldUserApi;
+import org.killbill.billing.util.api.TagUserApi;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.clock.Clock;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public abstract class ComboPaymentResource extends JaxRsResourceBase {
+
+    @Inject
+    public ComboPaymentResource(final JaxrsUriBuilder uriBuilder,
+                                final TagUserApi tagUserApi,
+                                final CustomFieldUserApi customFieldUserApi,
+                                final AuditUserApi auditUserApi,
+                                final AccountUserApi accountUserApi,
+                                final PaymentApi paymentApi,
+                                final Clock clock,
+                                final Context context) {
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+    }
+
+    protected Account getOrCreateAccount(final AccountJson accountJson, final CallContext callContext) throws AccountApiException {
+        // Attempt to retrieve by accountId if specified
+        if (accountJson.getAccountId() != null) {
+            return accountUserApi.getAccountById(UUID.fromString(accountJson.getAccountId()), callContext);
+        }
+
+        if (accountJson.getExternalKey() != null) {
+            // Attempt to retrieve by account externalKey, ignore if does not exist so we can create it with the key specified.
+            try {
+                return accountUserApi.getAccountByKey(accountJson.getExternalKey(), callContext);
+            } catch (final AccountApiException ignore) {
+            }
+        }
+        // Finally create if does not exist
+        return accountUserApi.createAccount(accountJson.toAccountData(), callContext);
+    }
+
+    protected UUID getOrCreatePaymentMethod(final Account account, final PaymentMethodJson paymentMethodJson, final Iterable<PluginProperty> pluginProperties, final CallContext callContext) throws PaymentApiException {
+        // Get all payment methods for account
+        final List<PaymentMethod> accountPaymentMethods = paymentApi.getAccountPaymentMethods(account.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
+
+        // If we were specified a paymentMethod id and we find it, we return it
+        if (paymentMethodJson.getPaymentMethodId() != null) {
+            final UUID match = UUID.fromString(paymentMethodJson.getPaymentMethodId());
+            if (Iterables.any(accountPaymentMethods, new Predicate<PaymentMethod>() {
+                @Override
+                public boolean apply(final PaymentMethod input) {
+                    return input.getId().equals(match);
+                }
+            })) {
+                return match;
+            }
+        }
+
+        // If we were specified a paymentMethod externalKey and we find it, we return it
+        if (paymentMethodJson.getExternalKey() != null) {
+            final PaymentMethod match = Iterables.tryFind(accountPaymentMethods, new Predicate<PaymentMethod>() {
+                @Override
+                public boolean apply(final PaymentMethod input) {
+                    return input.getExternalKey().equals(paymentMethodJson.getExternalKey());
+                }
+            }).orNull();
+            if (match != null) {
+                return match.getId();
+            }
+        }
+
+        // Only set as default if this is the first paymentMethod on the account
+        final boolean isDefault = accountPaymentMethods.isEmpty();
+        final PaymentMethod paymentData = paymentMethodJson.toPaymentMethod(account.getId().toString());
+        return paymentApi.addPaymentMethod(account, paymentMethodJson.getExternalKey(), paymentMethodJson.getPluginName(), isDefault,
+                                           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, pluginProperties, tenantContext);
+        } else {
+            return paymentApi.getPaymentByExternalKey(externalKey, false, pluginProperties, tenantContext);
+        }
+    }
+}
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 06581fc..2921882 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
@@ -54,6 +54,7 @@ import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.InvoicePaymentType;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
 import org.killbill.billing.jaxrs.json.JsonBase;
+import org.killbill.billing.jaxrs.json.PluginPropertyJson;
 import org.killbill.billing.jaxrs.json.TagJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
@@ -340,6 +341,20 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
         return null;
     }
 
+    protected Iterable<PluginProperty> extractPluginProperties(@Nullable final Iterable<PluginPropertyJson> pluginProperties) {
+        return pluginProperties != null ?
+               Iterables.<PluginPropertyJson, PluginProperty>transform(pluginProperties,
+                                                                       new Function<PluginPropertyJson, PluginProperty>() {
+                                                                           @Override
+                                                                           public PluginProperty apply(final PluginPropertyJson pluginPropertyJson) {
+                                                                               return pluginPropertyJson.toPluginProperty();
+                                                                           }
+                                                                       }
+                                                                      ) :
+               ImmutableList.<PluginProperty>of();
+
+    }
+
     protected Iterable<PluginProperty> extractPluginProperties(@Nullable final Iterable<String> pluginProperties, final PluginProperty... additionalProperties) {
         final Collection<PluginProperty> properties = new LinkedList<PluginProperty>();
         if (pluginProperties == null) {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java
index 609ef2d..7f50aa1 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -32,20 +32,18 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 
-import org.killbill.billing.ErrorCode;
 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.jaxrs.json.ComboHostedPaymentPageJson;
 import org.killbill.billing.jaxrs.json.GatewayNotificationJson;
 import org.killbill.billing.jaxrs.json.HostedPaymentPageFieldsJson;
 import org.killbill.billing.jaxrs.json.HostedPaymentPageFormDescriptorJson;
-import org.killbill.billing.jaxrs.json.PluginPropertyJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PaymentGatewayApi;
-import org.killbill.billing.payment.api.PaymentMethod;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.plugin.api.GatewayNotification;
 import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
@@ -56,10 +54,7 @@ import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.clock.Clock;
 
 import com.codahale.metrics.annotation.Timed;
-import com.google.common.base.Function;
 import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import com.google.inject.Singleton;
 import com.wordnik.swagger.annotations.Api;
 import com.wordnik.swagger.annotations.ApiOperation;
@@ -72,7 +67,7 @@ import static javax.ws.rs.core.MediaType.WILDCARD;
 @Singleton
 @Path(JaxrsResource.PAYMENT_GATEWAYS_PATH)
 @Api(value = JaxrsResource.PAYMENT_GATEWAYS_PATH, description = "HPP endpoints")
-public class PaymentGatewayResource extends JaxRsResourceBase {
+public class PaymentGatewayResource extends ComboPaymentResource {
 
     private final PaymentGatewayApi paymentGatewayApi;
 
@@ -92,6 +87,40 @@ public class PaymentGatewayResource extends JaxRsResourceBase {
 
     @Timed
     @POST
+    @Path("/" + HOSTED + "/" + FORM)
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Combo API to generate form data to redirect the customer to the gateway", response = HostedPaymentPageFormDescriptorJson.class)
+    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid data for Account or PaymentMethod")})
+    public Response buildComboFormDescriptor(final ComboHostedPaymentPageJson json,
+                                             @PathParam("accountId") final String accountIdString,
+                                             @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 {
+        verifyNonNullOrEmpty(json, "ComboHostedPaymentPageJson body should be specified");
+
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+
+        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+        final Account account = getOrCreateAccount(json.getAccount(), callContext);
+
+        final Iterable<PluginProperty> paymentMethodPluginProperties = extractPluginProperties(json.getPaymentMethodPluginProperties());
+        final UUID paymentMethodId = getOrCreatePaymentMethod(account, json.getPaymentMethod(), paymentMethodPluginProperties, callContext);
+
+        final HostedPaymentPageFieldsJson hostedPaymentPageFields = json.getHostedPaymentPageFieldsJson();
+        final Iterable<PluginProperty> customFields = extractPluginProperties(hostedPaymentPageFields != null ? hostedPaymentPageFields.getCustomFields() : null);
+
+        final HostedPaymentPageFormDescriptor descriptor = paymentGatewayApi.buildFormDescriptor(account, paymentMethodId, customFields, pluginProperties, callContext);
+        final HostedPaymentPageFormDescriptorJson result = new HostedPaymentPageFormDescriptorJson(descriptor);
+
+        return Response.status(Response.Status.OK).entity(result).build();
+    }
+
+    @Timed
+    @POST
     @Path("/" + HOSTED + "/" + FORM + "/{" + QUERY_ACCOUNT_ID + ":" + UUID_PATTERN + "}")
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
@@ -115,26 +144,14 @@ public class PaymentGatewayResource extends JaxRsResourceBase {
 
         validatePaymentMethodForAccount(accountId, paymentMethodId, callContext);
 
-        final Iterable<PluginProperty> customFields;
-        if (json == null) {
-            customFields = ImmutableList.<PluginProperty>of();
-        } else {
-            customFields = Iterables.<PluginPropertyJson, PluginProperty>transform(json.getCustomFields(),
-                                                                                   new Function<PluginPropertyJson, PluginProperty>() {
-                                                                                       @Override
-                                                                                       public PluginProperty apply(final PluginPropertyJson pluginPropertyJson) {
-                                                                                           return pluginPropertyJson.toPluginProperty();
-                                                                                       }
-                                                                                   }
-                                                                                  );
-        }
+        final Iterable<PluginProperty> customFields = extractPluginProperties(json.getCustomFields());
+
         final HostedPaymentPageFormDescriptor descriptor = paymentGatewayApi.buildFormDescriptor(account, paymentMethodId, customFields, pluginProperties, callContext);
         final HostedPaymentPageFormDescriptorJson result = new HostedPaymentPageFormDescriptorJson(descriptor);
 
         return Response.status(Response.Status.OK).entity(result).build();
     }
 
-
     @Timed
     @POST
     @Path("/" + NOTIFICATION + "/{" + QUERY_PAYMENT_PLUGIN_NAME + ":" + ANYTHING_PATTERN + "}")
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 42b942f..1122a21 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
@@ -46,18 +46,14 @@ 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.jaxrs.json.AccountJson;
 import org.killbill.billing.jaxrs.json.ComboPaymentTransactionJson;
 import org.killbill.billing.jaxrs.json.PaymentJson;
-import org.killbill.billing.jaxrs.json.PaymentMethodJson;
 import org.killbill.billing.jaxrs.json.PaymentTransactionJson;
-import org.killbill.billing.jaxrs.json.PluginPropertyJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
 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.PaymentMethod;
 import org.killbill.billing.payment.api.PaymentOptions;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionType;
@@ -72,12 +68,8 @@ import org.killbill.clock.Clock;
 
 import com.codahale.metrics.annotation.Timed;
 import com.google.common.base.Function;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
 import com.wordnik.swagger.annotations.Api;
 import com.wordnik.swagger.annotations.ApiOperation;
 import com.wordnik.swagger.annotations.ApiResponse;
@@ -87,7 +79,7 @@ import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 
 @Path(JaxrsResource.PAYMENTS_PATH)
 @Api(value = JaxrsResource.PAYMENTS_PATH, description = "Operations on payments")
-public class PaymentResource extends JaxRsResourceBase {
+public class PaymentResource extends ComboPaymentResource {
 
     @Inject
     public PaymentResource(final JaxrsUriBuilder uriBuilder,
@@ -484,17 +476,7 @@ public class PaymentResource extends JaxRsResourceBase {
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
         final Account account = getOrCreateAccount(json.getAccount(), callContext);
 
-        final Iterable<PluginProperty> paymentMethodPluginProperties = json.getPaymentMethodPluginProperties() != null ?
-                                                                       Iterables.<PluginPropertyJson, PluginProperty>transform(json.getPaymentMethodPluginProperties(),
-                                                                                                                               new Function<PluginPropertyJson, PluginProperty>() {
-                                                                                                                                   @Override
-                                                                                                                                   public PluginProperty apply(final PluginPropertyJson pluginPropertyJson) {
-                                                                                                                                       return pluginPropertyJson.toPluginProperty();
-                                                                                                                                   }
-                                                                                                                               }
-                                                                                                                              ) :
-                                                                       ImmutableList.<PluginProperty>of();
-
+        final Iterable<PluginProperty> paymentMethodPluginProperties = extractPluginProperties(json.getPaymentMethodPluginProperties());
         final UUID paymentMethodId = getOrCreatePaymentMethod(account, json.getPaymentMethod(), paymentMethodPluginProperties, callContext);
 
         final PaymentTransactionJson paymentTransactionJson = json.getTransaction();
@@ -502,16 +484,7 @@ public class PaymentResource extends JaxRsResourceBase {
         final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
         final Payment result;
 
-        final Iterable<PluginProperty> transactionPluginProperties = json.getTransactionPluginProperties() != null ?
-                                                                     Iterables.<PluginPropertyJson, PluginProperty>transform(json.getTransactionPluginProperties(),
-                                                                                                                             new Function<PluginPropertyJson, PluginProperty>() {
-                                                                                                                                 @Override
-                                                                                                                                 public PluginProperty apply(final PluginPropertyJson pluginPropertyJson) {
-                                                                                                                                     return pluginPropertyJson.toPluginProperty();
-                                                                                                                                 }
-                                                                                                                             }
-                                                                                                                            ) :
-                                                                     ImmutableList.<PluginProperty>of();
+        final Iterable<PluginProperty> transactionPluginProperties = extractPluginProperties(json.getTransactionPluginProperties());
 
         final Currency currency = paymentTransactionJson.getCurrency() == null ? account.getCurrency() : Currency.valueOf(paymentTransactionJson.getCurrency());
         final UUID paymentId = null; // If we need to specify a paymentId (e.g 3DS authorization, we can use regular API, no need for combo call)
@@ -541,70 +514,4 @@ public class PaymentResource extends JaxRsResourceBase {
     protected ObjectType getObjectType() {
         return ObjectType.PAYMENT;
     }
-
-    private Account getOrCreateAccount(final AccountJson accountJson, final CallContext callContext) throws AccountApiException {
-        // Attempt to retrieve by accountId if specified
-        if (accountJson.getAccountId() != null) {
-            return accountUserApi.getAccountById(UUID.fromString(accountJson.getAccountId()), callContext);
-        }
-
-        if (accountJson.getExternalKey() != null) {
-            // Attempt to retrieve by account externalKey, ignore if does not exist so we can create it with the key specified.
-            try {
-                return accountUserApi.getAccountByKey(accountJson.getExternalKey(), callContext);
-            } catch (final AccountApiException ignore) {}
-        }
-        // Finally create if does not exist
-        return accountUserApi.createAccount(accountJson.toAccountData(), callContext);
-    }
-
-    private UUID getOrCreatePaymentMethod(final Account account, final PaymentMethodJson paymentMethodJson, final Iterable<PluginProperty> pluginProperties, final CallContext callContext) throws PaymentApiException {
-
-        // Get all payment methods for account
-        final List<PaymentMethod> accountPaymentMethods = paymentApi.getAccountPaymentMethods(account.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
-
-        // If we were specified a paymentMethod id and we find it, we return it
-        if (paymentMethodJson.getPaymentMethodId() != null) {
-            final UUID match = UUID.fromString(paymentMethodJson.getPaymentMethodId());
-            if (Iterables.any(accountPaymentMethods, new Predicate<PaymentMethod>() {
-                @Override
-                public boolean apply(final PaymentMethod input) {
-                    return input.getId().equals(match);
-                }
-            })) {
-                return match;
-            }
-        }
-
-        // If we were specified a paymentMethod externalKey and we find it, we return it
-        if (paymentMethodJson.getExternalKey() != null) {
-            final PaymentMethod match = Iterables.tryFind(accountPaymentMethods, new Predicate<PaymentMethod>() {
-                @Override
-                public boolean apply(final PaymentMethod input) {
-                    return input.getExternalKey().equals(paymentMethodJson.getExternalKey());
-                }
-            }).orNull();
-            if (match != null) {
-                return match.getId();
-            }
-        }
-
-        // Only set as default if this is the first paymentMethod on the account
-        final boolean isDefault = accountPaymentMethods.isEmpty();
-        final PaymentMethod paymentData = paymentMethodJson.toPaymentMethod(account.getId().toString());
-        return paymentApi.addPaymentMethod(account, paymentMethodJson.getExternalKey(), paymentMethodJson.getPluginName(), isDefault,
-                                           paymentData.getPluginDetail(), pluginProperties, callContext);
-    }
-
-    private 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, pluginProperties, tenantContext);
-        } else {
-            return paymentApi.getPaymentByExternalKey(externalKey, false, pluginProperties, tenantContext);
-        }
-    }
-
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
index c142f18..dbc7541 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
@@ -39,6 +39,8 @@ import org.killbill.billing.payment.plugin.api.GatewayNotification;
 import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.payment.provider.DefaultNoOpGatewayNotification;
+import org.killbill.billing.payment.provider.DefaultNoOpHostedPaymentPageFormDescriptor;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
@@ -90,7 +92,7 @@ public class PaymentGatewayProcessor extends ProcessorBase {
                                                      final PaymentPluginApi plugin = getPaymentPluginApi(pluginName);
                                                      try {
                                                          final GatewayNotification result = plugin.processNotification(notification, properties, callContext);
-                                                         return PluginDispatcher.createPluginDispatcherReturnType(result);
+                                                         return PluginDispatcher.createPluginDispatcherReturnType(result == null ? new DefaultNoOpGatewayNotification() : result);
                                                      } catch (final PaymentPluginApiException e) {
                                                          throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage());
                                                      }
@@ -107,7 +109,7 @@ public class PaymentGatewayProcessor extends ProcessorBase {
 
                                                      try {
                                                          final HostedPaymentPageFormDescriptor result = plugin.buildFormDescriptor(account.getId(), customFields, properties, callContext);
-                                                         return PluginDispatcher.createPluginDispatcherReturnType(result);
+                                                         return PluginDispatcher.createPluginDispatcherReturnType(result == null ? new DefaultNoOpHostedPaymentPageFormDescriptor(account.getId()) : result);
                                                      } catch (final RuntimeException e) {
                                                          throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
                                                      } catch (final PaymentPluginApiException e) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpGatewayNotification.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpGatewayNotification.java
new file mode 100644
index 0000000..e377fab
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpGatewayNotification.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015 Groupon, Inc
+ * Copyright 2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.provider;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.plugin.api.GatewayNotification;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class DefaultNoOpGatewayNotification implements GatewayNotification {
+
+    @Override
+    public UUID getKbPaymentId() {
+        return null;
+    }
+
+    @Override
+    public int getStatus() {
+        return 200;
+    }
+
+    @Override
+    public String getEntity() {
+        return null;
+    }
+
+    @Override
+    public Map<String, List<String>> getHeaders() {
+        return ImmutableMap.<String, List<String>>of();
+    }
+
+    @Override
+    public List<PluginProperty> getProperties() {
+        return ImmutableList.<PluginProperty>of();
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpHostedPaymentPageFormDescriptor.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpHostedPaymentPageFormDescriptor.java
new file mode 100644
index 0000000..f623aaf
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpHostedPaymentPageFormDescriptor.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2015 Groupon, Inc
+ * Copyright 2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.provider;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
+
+import com.google.common.collect.ImmutableList;
+
+public class DefaultNoOpHostedPaymentPageFormDescriptor implements HostedPaymentPageFormDescriptor {
+
+    private final UUID kbAccountId;
+
+    public DefaultNoOpHostedPaymentPageFormDescriptor(final UUID kbAccountId) {
+        this.kbAccountId = kbAccountId;
+    }
+
+    @Override
+    public UUID getKbAccountId() {
+        return kbAccountId;
+    }
+
+    @Override
+    public String getFormMethod() {
+        return null;
+    }
+
+    @Override
+    public String getFormUrl() {
+        return null;
+    }
+
+    @Override
+    public List<PluginProperty> getFormFields() {
+        return ImmutableList.<PluginProperty>of();
+    }
+
+    @Override
+    public List<PluginProperty> getProperties() {
+        return ImmutableList.<PluginProperty>of();
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("DefaultNoOpHostedPaymentPageFormDescriptor{");
+        sb.append("kbAccountId=").append(kbAccountId);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultNoOpHostedPaymentPageFormDescriptor that = (DefaultNoOpHostedPaymentPageFormDescriptor) o;
+
+        return !(kbAccountId != null ? !kbAccountId.equals(that.kbAccountId) : that.kbAccountId != null);
+    }
+
+    @Override
+    public int hashCode() {
+        return kbAccountId != null ? kbAccountId.hashCode() : 0;
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java b/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java
index a6167a8..788bb28 100644
--- a/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java
+++ b/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -135,11 +135,11 @@ public class ExternalPaymentProviderPlugin implements PaymentPluginApi {
 
     @Override
     public HostedPaymentPageFormDescriptor buildFormDescriptor(final UUID kbAccountId, final Iterable<PluginProperty> customFields, final Iterable<PluginProperty> properties, final CallContext callContext) {
-        return null;
+        return new DefaultNoOpHostedPaymentPageFormDescriptor(kbAccountId);
     }
 
     @Override
     public GatewayNotification processNotification(final String notification, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentPluginApiException {
-        return null;
+        return new DefaultNoOpGatewayNotification();
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
index f161297..0d3e7ce 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -305,12 +305,12 @@ public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi {
 
     @Override
     public HostedPaymentPageFormDescriptor buildFormDescriptor(final UUID kbAccountId, final Iterable<PluginProperty> customFields, final Iterable<PluginProperty> properties, final CallContext callContext) {
-        return null;
+        return new DefaultNoOpHostedPaymentPageFormDescriptor(kbAccountId);
     }
 
     @Override
     public GatewayNotification processNotification(final String notification, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentPluginApiException {
-        return null;
+        return new DefaultNoOpGatewayNotification();
     }
 
     @Override

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index aeda3f9..e75a760 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.31</version>
+        <version>0.33-SNAPSHOT</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.15.2-SNAPSHOT</version>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentGateway.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentGateway.java
new file mode 100644
index 0000000..8da0baa
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentGateway.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2015 Groupon, Inc
+ * Copyright 2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs;
+
+import java.util.UUID;
+
+import org.killbill.billing.client.model.Account;
+import org.killbill.billing.client.model.ComboHostedPaymentPage;
+import org.killbill.billing.client.model.HostedPaymentPageFields;
+import org.killbill.billing.client.model.HostedPaymentPageFormDescriptor;
+import org.killbill.billing.client.model.PaymentMethod;
+import org.killbill.billing.client.model.PaymentMethodPluginDetail;
+import org.killbill.billing.client.model.PluginProperty;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.http.client.Response;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class TestPaymentGateway extends TestJaxrsBase {
+
+    @Test(groups = "slow")
+    public void testBuildFormDescriptor() throws Exception {
+        final Account account = createAccountWithDefaultPaymentMethod();
+
+        final HostedPaymentPageFields hppFields = new HostedPaymentPageFields();
+
+        final HostedPaymentPageFormDescriptor hostedPaymentPageFormDescriptor = killBillClient.buildFormDescriptor(hppFields, account.getAccountId(), null, ImmutableMap.<String, String>of(), createdBy, reason, comment);
+        Assert.assertEquals(hostedPaymentPageFormDescriptor.getKbAccountId(), account.getAccountId());
+    }
+
+    @Test(groups = "slow")
+    public void testComboBuildFormDescriptor() throws Exception {
+        final Account account = getAccount();
+        account.setAccountId(null);
+
+        final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail();
+        final PaymentMethod paymentMethod = new PaymentMethod(null, UUID.randomUUID().toString(), null, true, PLUGIN_NAME, info);
+
+        final HostedPaymentPageFields hppFields = new HostedPaymentPageFields();
+
+        final ComboHostedPaymentPage comboHostedPaymentPage = new ComboHostedPaymentPage(account, paymentMethod, ImmutableList.<PluginProperty>of(), hppFields);
+
+        final HostedPaymentPageFormDescriptor hostedPaymentPageFormDescriptor = killBillClient.buildFormDescriptor(comboHostedPaymentPage, ImmutableMap.<String, String>of(), createdBy, reason, comment);
+        Assert.assertNotNull(hostedPaymentPageFormDescriptor.getKbAccountId());
+    }
+
+    @Test(groups = "slow")
+    public void testProcessNotification() throws Exception {
+        final Response response = killBillClient.processNotification("TOTO", PLUGIN_NAME, ImmutableMap.<String, String>of(), createdBy, reason, comment);
+        Assert.assertEquals(response.getStatusCode(), 200);
+    }
+}