mirror of
https://github.com/google/nomulus.git
synced 2025-05-15 17:07:15 +02:00
Sync registrar to Braintree customer record automatically
In order to set the customerId field on a payment, the customer entry must exist in Braintree's database. This CL causes it to be created or updated before processing the payment. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=118530040
This commit is contained in:
parent
b3125ae070
commit
dcbabd06a6
8 changed files with 162 additions and 12 deletions
|
@ -6,8 +6,10 @@ java_library(
|
||||||
srcs = glob(["*.java"]),
|
srcs = glob(["*.java"]),
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//java/com/google/common/base",
|
||||||
"//java/com/google/domain/registry/config",
|
"//java/com/google/domain/registry/config",
|
||||||
"//java/com/google/domain/registry/keyring/api",
|
"//java/com/google/domain/registry/keyring/api",
|
||||||
|
"//java/com/google/domain/registry/model",
|
||||||
"//third_party/java/braintree",
|
"//third_party/java/braintree",
|
||||||
"//third_party/java/dagger",
|
"//third_party/java/dagger",
|
||||||
"//third_party/java/jsr305_annotations",
|
"//third_party/java/jsr305_annotations",
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed 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 com.google.domain.registry.braintree;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Verify.verify;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.google.common.base.VerifyException;
|
||||||
|
import com.google.domain.registry.model.registrar.Registrar;
|
||||||
|
import com.google.domain.registry.model.registrar.RegistrarContact;
|
||||||
|
|
||||||
|
import com.braintreegateway.BraintreeGateway;
|
||||||
|
import com.braintreegateway.Customer;
|
||||||
|
import com.braintreegateway.CustomerRequest;
|
||||||
|
import com.braintreegateway.Result;
|
||||||
|
import com.braintreegateway.exceptions.NotFoundException;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/** Helper for creating Braintree customer entries for registrars. */
|
||||||
|
public class BraintreeRegistrarSyncer {
|
||||||
|
|
||||||
|
private final BraintreeGateway braintree;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
BraintreeRegistrarSyncer(BraintreeGateway braintreeGateway) {
|
||||||
|
this.braintree = braintreeGateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs {@code registrar} with Braintree customer entry, creating it if one doesn't exist.
|
||||||
|
*
|
||||||
|
* <p>The customer ID will be the same as {@link Registrar#getClientIdentifier()}.
|
||||||
|
*
|
||||||
|
* <p>Creating a customer object in Braintree's database is a necessary step in order to associate
|
||||||
|
* a payment with a registrar. The transaction will fail if the customer object doesn't exist.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if {@code registrar} is not using BRAINTREE billing
|
||||||
|
* @throws VerifyException if the Braintree API returned a failure response
|
||||||
|
*/
|
||||||
|
public void sync(Registrar registrar) throws VerifyException {
|
||||||
|
String id = registrar.getClientIdentifier();
|
||||||
|
checkArgument(registrar.getBillingMethod() == Registrar.BillingMethod.BRAINTREE,
|
||||||
|
"Registrar (%s) billing method (%s) is not BRAINTREE", id, registrar.getBillingMethod());
|
||||||
|
CustomerRequest request = createRequest(registrar);
|
||||||
|
Result<Customer> result;
|
||||||
|
if (doesCustomerExist(id)) {
|
||||||
|
result = braintree.customer().update(id, request);
|
||||||
|
} else {
|
||||||
|
result = braintree.customer().create(request);
|
||||||
|
}
|
||||||
|
verify(result.isSuccess(),
|
||||||
|
"Failed to sync registrar (%s) to braintree customer: %s", id, result.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private CustomerRequest createRequest(Registrar registrar) {
|
||||||
|
CustomerRequest result =
|
||||||
|
new CustomerRequest()
|
||||||
|
.id(registrar.getClientIdentifier())
|
||||||
|
.customerId(registrar.getClientIdentifier())
|
||||||
|
.company(registrar.getRegistrarName());
|
||||||
|
Optional<RegistrarContact> contact = getBillingContact(registrar);
|
||||||
|
if (contact.isPresent()) {
|
||||||
|
result.email(contact.get().getEmailAddress());
|
||||||
|
result.phone(contact.get().getPhoneNumber());
|
||||||
|
result.fax(contact.get().getFaxNumber());
|
||||||
|
} else {
|
||||||
|
result.email(registrar.getEmailAddress());
|
||||||
|
result.phone(registrar.getPhoneNumber());
|
||||||
|
result.fax(registrar.getFaxNumber());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<RegistrarContact> getBillingContact(Registrar registrar) {
|
||||||
|
for (RegistrarContact contact : registrar.getContacts()) {
|
||||||
|
if (contact.getTypes().contains(RegistrarContact.Type.BILLING)) {
|
||||||
|
return Optional.of(contact);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean doesCustomerExist(String id) {
|
||||||
|
try {
|
||||||
|
braintree.customer().find(id);
|
||||||
|
return true;
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ java_library(
|
||||||
"//java/com/google/common/collect",
|
"//java/com/google/common/collect",
|
||||||
"//java/com/google/common/io",
|
"//java/com/google/common/io",
|
||||||
"//java/com/google/common/net",
|
"//java/com/google/common/net",
|
||||||
|
"//java/com/google/domain/registry/braintree",
|
||||||
"//java/com/google/domain/registry/config",
|
"//java/com/google/domain/registry/config",
|
||||||
"//java/com/google/domain/registry/export/sheet",
|
"//java/com/google/domain/registry/export/sheet",
|
||||||
"//java/com/google/domain/registry/flows",
|
"//java/com/google/domain/registry/flows",
|
||||||
|
|
|
@ -21,6 +21,7 @@ import static java.util.Arrays.asList;
|
||||||
|
|
||||||
import com.google.common.collect.FluentIterable;
|
import com.google.common.collect.FluentIterable;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.domain.registry.braintree.BraintreeRegistrarSyncer;
|
||||||
import com.google.domain.registry.config.ConfigModule.Config;
|
import com.google.domain.registry.config.ConfigModule.Config;
|
||||||
import com.google.domain.registry.config.RegistryEnvironment;
|
import com.google.domain.registry.config.RegistryEnvironment;
|
||||||
import com.google.domain.registry.model.registrar.Registrar;
|
import com.google.domain.registry.model.registrar.Registrar;
|
||||||
|
@ -76,6 +77,7 @@ import javax.inject.Inject;
|
||||||
public final class RegistrarPaymentSetupAction implements Runnable, JsonAction {
|
public final class RegistrarPaymentSetupAction implements Runnable, JsonAction {
|
||||||
|
|
||||||
@Inject BraintreeGateway braintreeGateway;
|
@Inject BraintreeGateway braintreeGateway;
|
||||||
|
@Inject BraintreeRegistrarSyncer customerSyncer;
|
||||||
@Inject JsonActionRunner jsonActionRunner;
|
@Inject JsonActionRunner jsonActionRunner;
|
||||||
@Inject Registrar registrar;
|
@Inject Registrar registrar;
|
||||||
@Inject RegistryEnvironment environment;
|
@Inject RegistryEnvironment environment;
|
||||||
|
@ -93,6 +95,7 @@ public final class RegistrarPaymentSetupAction implements Runnable, JsonAction {
|
||||||
if (!json.isEmpty()) {
|
if (!json.isEmpty()) {
|
||||||
return JsonResponseHelper.create(ERROR, "JSON request object must be empty");
|
return JsonResponseHelper.create(ERROR, "JSON request object must be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
// payment.js is hard-coded to display a specific SOY error template when encountering the
|
// payment.js is hard-coded to display a specific SOY error template when encountering the
|
||||||
// following error messages.
|
// following error messages.
|
||||||
if (environment == RegistryEnvironment.SANDBOX) {
|
if (environment == RegistryEnvironment.SANDBOX) {
|
||||||
|
@ -102,6 +105,10 @@ public final class RegistrarPaymentSetupAction implements Runnable, JsonAction {
|
||||||
// Registrar needs to contact support to have their billing bit flipped.
|
// Registrar needs to contact support to have their billing bit flipped.
|
||||||
return JsonResponseHelper.create(ERROR, "not-using-cc-billing");
|
return JsonResponseHelper.create(ERROR, "not-using-cc-billing");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In order to set the customerId field on the payment, the customer must exist.
|
||||||
|
customerSyncer.sync(registrar);
|
||||||
|
|
||||||
return JsonResponseHelper
|
return JsonResponseHelper
|
||||||
.create(SUCCESS, "Success", asList(
|
.create(SUCCESS, "Success", asList(
|
||||||
ImmutableMap.of(
|
ImmutableMap.of(
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed 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 com.google.domain.registry.testing;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
/** Utility class for extracting encapsulated contents of objects for testing. */
|
||||||
|
public final class ReflectiveFieldExtractor {
|
||||||
|
|
||||||
|
/** Extracts private {@code fieldName} on {@code object} without public getter. */
|
||||||
|
public static <T> T extractField(Class<T> returnType, Object object, String fieldName)
|
||||||
|
throws NoSuchFieldException, SecurityException, IllegalAccessException {
|
||||||
|
Field field = object.getClass().getDeclaredField(fieldName);
|
||||||
|
field.setAccessible(true);
|
||||||
|
Object value = field.get(object);
|
||||||
|
checkArgument(value == null || returnType.isInstance(value));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
T result = (T) value;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReflectiveFieldExtractor() {}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ java_library(
|
||||||
"//java/com/google/common/io",
|
"//java/com/google/common/io",
|
||||||
"//java/com/google/common/net",
|
"//java/com/google/common/net",
|
||||||
"//java/com/google/common/testing",
|
"//java/com/google/common/testing",
|
||||||
|
"//java/com/google/domain/registry/braintree",
|
||||||
"//java/com/google/domain/registry/config",
|
"//java/com/google/domain/registry/config",
|
||||||
"//java/com/google/domain/registry/export/sheet",
|
"//java/com/google/domain/registry/export/sheet",
|
||||||
"//java/com/google/domain/registry/model",
|
"//java/com/google/domain/registry/model",
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
|
|
||||||
package com.google.domain.registry.ui.server.registrar;
|
package com.google.domain.registry.ui.server.registrar;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static com.google.domain.registry.testing.ReflectiveFieldExtractor.extractField;
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
@ -45,7 +45,6 @@ import org.mockito.Captor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.runners.MockitoJUnitRunner;
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/** Tests for {@link RegistrarPaymentAction}. */
|
/** Tests for {@link RegistrarPaymentAction}. */
|
||||||
|
@ -444,14 +443,4 @@ public class RegistrarPaymentActionTest {
|
||||||
"field", "amount",
|
"field", "amount",
|
||||||
"results", asList());
|
"results", asList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static <T> T extractField(Class<T> returnType, Object object, String fieldName)
|
|
||||||
throws NoSuchFieldException, SecurityException, IllegalAccessException {
|
|
||||||
Field field = object.getClass().getDeclaredField(fieldName);
|
|
||||||
field.setAccessible(true);
|
|
||||||
Object value = field.get(object);
|
|
||||||
checkArgument(returnType.isInstance(value));
|
|
||||||
return (T) value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,12 @@ package com.google.domain.registry.ui.server.registrar;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.domain.registry.braintree.BraintreeRegistrarSyncer;
|
||||||
import com.google.domain.registry.config.RegistryEnvironment;
|
import com.google.domain.registry.config.RegistryEnvironment;
|
||||||
import com.google.domain.registry.model.registrar.Registrar;
|
import com.google.domain.registry.model.registrar.Registrar;
|
||||||
import com.google.domain.registry.testing.AppEngineRule;
|
import com.google.domain.registry.testing.AppEngineRule;
|
||||||
|
@ -49,11 +52,15 @@ public class RegistrarPaymentSetupActionTest {
|
||||||
@Mock
|
@Mock
|
||||||
private ClientTokenGateway clientTokenGateway;
|
private ClientTokenGateway clientTokenGateway;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private BraintreeRegistrarSyncer customerSyncer;
|
||||||
|
|
||||||
private final RegistrarPaymentSetupAction action = new RegistrarPaymentSetupAction();
|
private final RegistrarPaymentSetupAction action = new RegistrarPaymentSetupAction();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() throws Exception {
|
public void before() throws Exception {
|
||||||
action.braintreeGateway = braintreeGateway;
|
action.braintreeGateway = braintreeGateway;
|
||||||
|
action.customerSyncer = customerSyncer;
|
||||||
action.registrar =
|
action.registrar =
|
||||||
Registrar.loadByClientId("TheRegistrar").asBuilder()
|
Registrar.loadByClientId("TheRegistrar").asBuilder()
|
||||||
.setBillingMethod(Registrar.BillingMethod.BRAINTREE)
|
.setBillingMethod(Registrar.BillingMethod.BRAINTREE)
|
||||||
|
@ -79,6 +86,7 @@ public class RegistrarPaymentSetupActionTest {
|
||||||
"token", blanketsOfSadness,
|
"token", blanketsOfSadness,
|
||||||
"currencies", asList("USD", "JPY"),
|
"currencies", asList("USD", "JPY"),
|
||||||
"brainframe", "/doodle")));
|
"brainframe", "/doodle")));
|
||||||
|
verify(customerSyncer).sync(eq(action.registrar));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue