Delete all Braintree code

We never launched this, don't planning on launching it now anyway, and it's rotted over the past two years anyway.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=202993577
This commit is contained in:
mcilwain 2018-07-02 12:24:54 -07:00 committed by jianglai
parent 7023b818b7
commit 32b3563126
67 changed files with 28 additions and 3352 deletions

View file

@ -1,20 +0,0 @@
package(
default_visibility = ["//visibility:public"],
)
licenses(["notice"]) # Apache 2.0
java_library(
name = "braintree",
srcs = glob(["*.java"]),
deps = [
"//java/google/registry/config",
"//java/google/registry/keyring/api",
"//java/google/registry/model",
"@com_braintreepayments_gateway_braintree_java",
"@com_google_code_findbugs_jsr305",
"@com_google_dagger",
"@com_google_guava",
"@javax_inject",
],
)

View file

@ -1,44 +0,0 @@
// Copyright 2017 The Nomulus 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 google.registry.braintree;
import com.braintreegateway.BraintreeGateway;
import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryEnvironment;
import google.registry.keyring.api.KeyModule.Key;
import javax.inject.Singleton;
/** Dagger module for Braintree Payments API. */
@Module
public final class BraintreeModule {
@Provides
@Singleton
static BraintreeGateway provideBraintreeGateway(
RegistryEnvironment environment,
@Config("braintreeMerchantId") String merchantId,
@Config("braintreePublicKey") String publicKey,
@Key("braintreePrivateKey") String privateKey) {
return new BraintreeGateway(
environment == RegistryEnvironment.PRODUCTION
? com.braintreegateway.Environment.PRODUCTION
: com.braintreegateway.Environment.SANDBOX,
merchantId,
publicKey,
privateKey);
}
}

View file

@ -1,103 +0,0 @@
// Copyright 2017 The Nomulus 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 google.registry.braintree;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Verify.verify;
import com.braintreegateway.BraintreeGateway;
import com.braintreegateway.Customer;
import com.braintreegateway.CustomerRequest;
import com.braintreegateway.Result;
import com.braintreegateway.exceptions.NotFoundException;
import com.google.common.base.VerifyException;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarContact;
import java.util.Optional;
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#getClientId()}.
*
* <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) {
String id = registrar.getClientId();
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.getClientId())
.customerId(registrar.getClientId())
.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.empty();
}
private boolean doesCustomerExist(String id) {
try {
braintree.customer().find(id);
return true;
} catch (NotFoundException e) {
return false;
}
}
}

View file

@ -1,17 +0,0 @@
// Copyright 2017 The Nomulus 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.
/** Braintree payment gateway utilities. */
@javax.annotation.ParametersAreNonnullByDefault
package google.registry.braintree;

View file

@ -32,14 +32,11 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.net.URI;
import java.net.URL;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Named;
import javax.inject.Qualifier;
import javax.inject.Singleton;
import org.joda.money.CurrencyUnit;
import org.joda.time.DateTimeConstants;
import org.joda.time.Duration;
@ -887,51 +884,6 @@ public final class RegistryConfig {
return null;
}
/**
* Returns Braintree Merchant Account IDs for each supported currency.
*
* @see google.registry.ui.server.registrar.RegistrarPaymentAction
* @see google.registry.ui.server.registrar.RegistrarPaymentSetupAction
*/
@Provides
@Config("braintreeMerchantAccountIds")
public static ImmutableMap<CurrencyUnit, String> provideBraintreeMerchantAccountId(
RegistryConfigSettings config) {
Map<String, String> merchantAccountIds = config.braintree.merchantAccountIdsMap;
ImmutableMap.Builder<CurrencyUnit, String> builder = new ImmutableMap.Builder<>();
for (Entry<String, String> entry : merchantAccountIds.entrySet()) {
builder.put(CurrencyUnit.of(entry.getKey()), entry.getValue());
}
return builder.build();
}
/**
* Returns Braintree Merchant ID of Registry, used for accessing Braintree API.
*
* <p>This is a base32 value copied from the Braintree website.
*
* @see google.registry.braintree.BraintreeModule
*/
@Provides
@Config("braintreeMerchantId")
public static String provideBraintreeMerchantId(RegistryConfigSettings config) {
return config.braintree.merchantId;
}
/**
* Returns Braintree Public Key of Registry, used for accessing Braintree API.
*
* <p>This is a base32 value copied from the Braintree website.
*
* @see google.registry.braintree.BraintreeModule
* @see google.registry.keyring.api.Keyring#getBraintreePrivateKey()
*/
@Provides
@Config("braintreePublicKey")
public static String provideBraintreePublicKey(RegistryConfigSettings config) {
return config.braintree.publicKey;
}
/**
* Disclaimer displayed at the end of WHOIS query results.
*

View file

@ -15,7 +15,6 @@
package google.registry.config;
import java.util.List;
import java.util.Map;
/** The POJO that YAML config files are deserialized into. */
public class RegistryConfigSettings {
@ -33,7 +32,6 @@ public class RegistryConfigSettings {
public RegistrarConsole registrarConsole;
public Monitoring monitoring;
public Misc misc;
public Braintree braintree;
public Kms kms;
public RegistryTool registryTool;
@ -158,13 +156,6 @@ public class RegistryConfigSettings {
public int asyncDeleteDelaySeconds;
}
/** Configuration for Braintree credit card payment processing. */
public static class Braintree {
public String merchantId;
public String publicKey;
public Map<String, String> merchantAccountIdsMap;
}
/** Configuration options for the registry tool. */
public static class RegistryTool {
public String clientSecretFilename;

View file

@ -246,21 +246,6 @@ misc:
# hosts from being used on domains.
asyncDeleteDelaySeconds: 90
# Braintree is a credit card payment processor that is used on the registrar
# console to allow registrars to pay their invoices.
braintree:
# Merchant ID of the Braintree account.
merchantId: example
# Public key used for accessing Braintree API (this is found on their site).
publicKey: example
# A map of JODA Money CurrencyUnits, specified in three letter ISO-4217
# format, to Braintree account IDs (each account is limited to a single
# currency). For example, one entry might be:
# USD: accountIdUsingUSD
merchantAccountIdsMap: {}
kms:
# GCP project containing the KMS keyring. Should only be used for KMS in
# order to keep a simple locked down IAM configuration.

View file

@ -57,15 +57,5 @@ registrarConsole:
misc:
sheetExportId: placeholder
# You only need to specify this section if using Braintree.
braintree:
merchantId: placeholder
publicKey: placeholder
# Only include currencies that you use.
merchantAccountIdsMap:
EUR: placeholder
JPY: placeholder
USD: placeholder
kms:
projectId: placeholder

View file

@ -19,8 +19,3 @@ caching:
staticPremiumListMaxCachedEntries: 50
eppResourceCachingEnabled: true
eppResourceCachingSeconds: 0
braintree:
merchantAccountIdsMap:
USD: accountIdUsd
JPY: accountIdJpy

View file

@ -31,18 +31,6 @@
<url-pattern>/registrar</url-pattern>
</servlet-mapping>
<!-- Registrar Braintree payment form setup. -->
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
<url-pattern>/registrar-payment-setup</url-pattern>
</servlet-mapping>
<!-- Registrar Braintree payment. -->
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
<url-pattern>/registrar-payment</url-pattern>
</servlet-mapping>
<!-- Registrar Self-serve Settings. -->
<servlet-mapping>
<servlet-name>frontend-servlet</servlet-name>
@ -73,7 +61,6 @@
<!-- TODO(b/26776367): Move these files to /assets/sources. -->
<url-pattern>/assets/js/registrar_bin.js.map</url-pattern>
<url-pattern>/assets/js/registrar_dbg.js</url-pattern>
<url-pattern>/assets/js/brain_bin.js.map</url-pattern>
<url-pattern>/assets/css/registrar_dbg.css</url-pattern>
</web-resource-collection>

View file

@ -61,7 +61,6 @@
<!-- TODO(b/26776367): Move these files to /assets/sources. -->
<url-pattern>/assets/js/registrar_bin.js.map</url-pattern>
<url-pattern>/assets/js/registrar_dbg.js</url-pattern>
<url-pattern>/assets/js/brain_bin.js.map</url-pattern>
<url-pattern>/assets/css/registrar_dbg.css</url-pattern>
</web-resource-collection>

View file

@ -109,7 +109,6 @@ public final class DummyKeyringModule {
"not a real login",
"not a real password",
"not a real login",
"not a real credential",
"not a real key");
"not a real credential");
}
}

View file

@ -38,7 +38,6 @@ public final class InMemoryKeyring implements Keyring {
private final String marksdbLordnPassword;
private final String marksdbSmdrlLogin;
private final String jsonCredential;
private final String braintreePrivateKey;
public InMemoryKeyring(
PGPKeyPair rdeStagingKey,
@ -52,8 +51,7 @@ public final class InMemoryKeyring implements Keyring {
String marksdbDnlLogin,
String marksdbLordnPassword,
String marksdbSmdrlLogin,
String jsonCredential,
String braintreePrivateKey) {
String jsonCredential) {
checkArgument(PgpHelper.isSigningKey(rdeSigningKey.getPublicKey()),
"RDE signing key must support signing: %s", rdeSigningKey.getKeyID());
checkArgument(rdeStagingKey.getPublicKey().isEncryptionKey(),
@ -76,7 +74,6 @@ public final class InMemoryKeyring implements Keyring {
this.marksdbLordnPassword = checkNotNull(marksdbLordnPassword, "marksdbLordnPassword");
this.marksdbSmdrlLogin = checkNotNull(marksdbSmdrlLogin, "marksdbSmdrlLogin");
this.jsonCredential = checkNotNull(jsonCredential, "jsonCredential");
this.braintreePrivateKey = checkNotNull(braintreePrivateKey, "braintreePrivateKey");
}
@Override
@ -144,11 +141,6 @@ public final class InMemoryKeyring implements Keyring {
return jsonCredential;
}
@Override
public String getBraintreePrivateKey() {
return braintreePrivateKey;
}
/** Does nothing. */
@Override
public void close() {}

View file

@ -120,10 +120,4 @@ public final class KeyModule {
static String provideJsonCredential(Keyring keyring) {
return keyring.getJsonCredential();
}
@Provides
@Key("braintreePrivateKey")
static String provideBraintreePrivateKey(Keyring keyring) {
return keyring.getBraintreePrivateKey();
}
}

View file

@ -149,15 +149,6 @@ public interface Keyring extends AutoCloseable {
*/
String getJsonCredential();
/**
* Returns Braintree API private key for Registry.
*
* <p>This is a base32 value copied from the Braintree website.
*
* @see google.registry.config.RegistryConfig.ConfigModule#provideBraintreePublicKey
*/
String getBraintreePrivateKey();
// Don't throw so try-with-resources works better.
@Override
void close();

View file

@ -64,7 +64,6 @@ public class KmsKeyring implements Keyring {
}
enum StringKeyLabel {
BRAINTREE_PRIVATE_KEY_STRING,
ICANN_REPORTING_PASSWORD_STRING,
JSON_CREDENTIAL_STRING,
MARKSDB_DNL_LOGIN_STRING,
@ -150,11 +149,6 @@ public class KmsKeyring implements Keyring {
return getString(StringKeyLabel.JSON_CREDENTIAL_STRING);
}
@Override
public String getBraintreePrivateKey() {
return getString(StringKeyLabel.BRAINTREE_PRIVATE_KEY_STRING);
}
/** No persistent resources are maintained for this Keyring implementation. */
@Override
public void close() {}

View file

@ -24,7 +24,6 @@ import static google.registry.keyring.kms.KmsKeyring.PublicKeyLabel.BRDA_SIGNING
import static google.registry.keyring.kms.KmsKeyring.PublicKeyLabel.RDE_RECEIVER_PUBLIC;
import static google.registry.keyring.kms.KmsKeyring.PublicKeyLabel.RDE_SIGNING_PUBLIC;
import static google.registry.keyring.kms.KmsKeyring.PublicKeyLabel.RDE_STAGING_PUBLIC;
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.BRAINTREE_PRIVATE_KEY_STRING;
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.ICANN_REPORTING_PASSWORD_STRING;
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.JSON_CREDENTIAL_STRING;
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.MARKSDB_DNL_LOGIN_STRING;
@ -116,10 +115,6 @@ public final class KmsUpdater {
return setString(credential, JSON_CREDENTIAL_STRING);
}
public KmsUpdater setBraintreePrivateKey(String braintreePrivateKey) {
return setString(braintreePrivateKey, BRAINTREE_PRIVATE_KEY_STRING);
}
/**
* Generates new encryption keys in KMS, encrypts the updated secrets with them, and persists the
* encrypted secrets to Datastore.

View file

@ -158,16 +158,6 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
SUSPENDED;
}
/** Method for acquiring money from a registrar customer. */
public enum BillingMethod {
/** Billing method where billing invoice data is exported to an external accounting system. */
EXTERNAL,
/** Billing method where we accept Braintree credit card payments in the Registrar Console. */
BRAINTREE;
}
/** Regex for E.164 phone number format specified by {@code contact.xsd}. */
private static final Pattern E164_PATTERN = Pattern.compile("\\+[0-9]{1,3}\\.[0-9]{1,14}");
@ -398,16 +388,6 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
*/
boolean contactsRequireSyncing = true;
/**
* Method for receiving money from a registrar customer.
*
* <p>Each registrar may opt-in to their preferred billing method. This value can be changed at
* any time using the {@code update_registrar} command.
*
* <p><b>Note:</b> This value should not be changed if the balance is non-zero.
*/
BillingMethod billingMethod;
/** Whether the registrar must acknowledge the price to register non-standard-priced domains. */
boolean premiumPriceAckRequired;
@ -555,10 +535,6 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
return driveFolderId;
}
public BillingMethod getBillingMethod() {
return firstNonNull(billingMethod, BillingMethod.EXTERNAL);
}
/**
* Returns a list of all {@link RegistrarContact} objects for this registrar sorted by their email
* address.
@ -834,11 +810,6 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
return this;
}
public Builder setBillingMethod(BillingMethod billingMethod) {
getInstance().billingMethod = billingMethod;
return this;
}
public Builder setPassword(String password) {
// Passwords must be [6,16] chars long. See "pwType" in the base EPP schema of RFC 5730.
checkArgument(

View file

@ -8,7 +8,6 @@ java_library(
name = "frontend",
srcs = glob(["*.java"]),
deps = [
"//java/google/registry/braintree",
"//java/google/registry/config",
"//java/google/registry/dns",
"//java/google/registry/flows",

View file

@ -17,7 +17,6 @@ package google.registry.module.frontend;
import com.google.monitoring.metrics.MetricReporter;
import dagger.Component;
import dagger.Lazy;
import google.registry.braintree.BraintreeModule;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.flows.ServerTridProviderModule;
import google.registry.flows.custom.CustomLogicFactoryModule;
@ -34,7 +33,7 @@ import google.registry.request.Modules.UrlFetchTransportModule;
import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.request.auth.AuthModule;
import google.registry.ui.ConsoleConfigModule;
import google.registry.ui.ConsoleDebug.ConsoleConfigModule;
import google.registry.util.SystemClock.SystemClockModule;
import google.registry.util.SystemSleeper.SystemSleeperModule;
import javax.inject.Singleton;
@ -45,7 +44,6 @@ import javax.inject.Singleton;
modules = {
AppIdentityCredentialModule.class,
AuthModule.class,
BraintreeModule.class,
ConfigModule.class,
ConsoleConfigModule.class,
CustomLogicFactoryModule.class,

View file

@ -26,8 +26,6 @@ import google.registry.request.RequestComponentBuilder;
import google.registry.request.RequestModule;
import google.registry.request.RequestScope;
import google.registry.ui.server.registrar.ConsoleUiAction;
import google.registry.ui.server.registrar.RegistrarPaymentAction;
import google.registry.ui.server.registrar.RegistrarPaymentSetupAction;
import google.registry.ui.server.registrar.RegistrarSettingsAction;
/** Dagger component with per-request lifetime for "default" App Engine module. */
@ -44,8 +42,6 @@ interface FrontendRequestComponent {
EppConsoleAction eppConsoleAction();
EppTlsAction eppTlsAction();
FlowComponent.Builder flowComponentBuilder();
RegistrarPaymentAction registrarPaymentAction();
RegistrarPaymentSetupAction registrarPaymentSetupAction();
RegistrarSettingsAction registrarSettingsAction();
@Subcomponent.Builder

View file

@ -23,7 +23,6 @@ def domain_registry_bazel_check():
def domain_registry_repositories(
omit_com_beust_jcommander=False,
omit_com_braintreepayments_gateway_braintree_java=False,
omit_com_fasterxml_jackson_core=False,
omit_com_fasterxml_jackson_core_jackson_annotations=False,
omit_com_fasterxml_jackson_core_jackson_databind=False,
@ -163,8 +162,6 @@ def domain_registry_repositories(
domain_registry_bazel_check()
if not omit_com_beust_jcommander:
com_beust_jcommander()
if not omit_com_braintreepayments_gateway_braintree_java:
com_braintreepayments_gateway_braintree_java()
if not omit_com_fasterxml_jackson_core:
com_fasterxml_jackson_core()
if not omit_com_fasterxml_jackson_core_jackson_annotations:
@ -447,17 +444,6 @@ def com_beust_jcommander():
licenses = ["notice"], # The Apache Software License, Version 2.0
)
def com_braintreepayments_gateway_braintree_java():
java_import_external(
name = "com_braintreepayments_gateway_braintree_java",
jar_sha256 = "e6fa51822d05334971d60a8353d4bfcab155b9639d9d8d3d052fe75ead534dd9",
jar_urls = [
"http://domain-registry-maven.storage.googleapis.com/repo1.maven.org/maven2/com/braintreepayments/gateway/braintree-java/2.54.0/braintree-java-2.54.0.jar",
"http://repo1.maven.org/maven2/com/braintreepayments/gateway/braintree-java/2.54.0/braintree-java-2.54.0.jar",
],
licenses = ["notice"], # MIT license
)
def com_fasterxml_jackson_core():
java_import_external(
name = "com_fasterxml_jackson_core",

View file

@ -29,7 +29,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.BillingMethod;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registry.Registry;
import google.registry.tools.params.KeyValueMapParameter.CurrencyUnitToStringMap;
@ -187,12 +186,6 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
)
private Map<CurrencyUnit, String> billingAccountMap;
@Nullable
@Parameter(
names = "--billing_method",
description = "Method by which registry bills this registrar customer")
private BillingMethod billingMethod;
@Nullable
@Parameter(
names = "--street",
@ -367,7 +360,6 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
newBillingAccountMap.putAll(billingAccountMap);
builder.setBillingAccountMap(newBillingAccountMap);
}
Optional.ofNullable(billingMethod).ifPresent(builder::setBillingMethod);
List<Object> streetAddressFields = Arrays.asList(street, city, state, zip, countryCode);
checkArgument(
streetAddressFields.stream().anyMatch(isNull())

View file

@ -57,9 +57,6 @@ final class GetKeyringSecretCommand implements RemoteApiCommand {
Security.addProvider(new BouncyCastleProvider());
switch (keyringKeyName) {
case BRAINTREE_PRIVATE_KEY:
out.write(KeySerializer.serializeString(keyring.getBraintreePrivateKey()));
break;
case BRDA_RECEIVER_PUBLIC_KEY:
out.write(KeySerializer.serializePublicKey(keyring.getBrdaReceiverKey()));
break;

View file

@ -56,9 +56,6 @@ final class UpdateKmsKeyringCommand implements RemoteApiCommand {
byte[] input = Files.readAllBytes(inputPath);
switch (keyringKeyName) {
case BRAINTREE_PRIVATE_KEY:
kmsUpdater.setBraintreePrivateKey(deserializeString(input));
break;
case BRDA_RECEIVER_PUBLIC_KEY:
kmsUpdater.setBrdaReceiverPublicKey(deserializePublicKey(input));
break;

View file

@ -21,7 +21,6 @@ package google.registry.tools.params;
* any secret update command such as UpdateKmsKeyringCommand.
*/
public enum KeyringKeyName {
BRAINTREE_PRIVATE_KEY,
BRDA_RECEIVER_PUBLIC_KEY,
BRDA_SIGNING_KEY_PAIR,
BRDA_SIGNING_PUBLIC_KEY,

View file

@ -10,7 +10,6 @@ exports_files(["globals.txt"])
filegroup(
name = "runfiles",
srcs = glob(["assets/**"]) + [
"brain_bin.js",
"registrar_bin.js",
"//java/google/registry/ui/css:registrar_bin.css",
"//java/google/registry/ui/html:html_files",
@ -27,7 +26,6 @@ filegroup(
filegroup(
name = "runfiles_debug",
srcs = [
":brain_bin",
":deps",
":registrar_bin",
":registrar_dbg",
@ -63,7 +61,6 @@ zip_file(
out = "ui_debug.war",
data = [":runfiles_debug"],
mappings = {
"domain_registry/java/google/registry/ui/brain_bin.js.map": "assets/js/brain_bin.js.map",
"domain_registry/java/google/registry/ui/registrar_bin.js.map": "assets/js/registrar_bin.js.map",
"domain_registry/java/google/registry/ui/registrar_dbg.js": "assets/js/registrar_dbg.js",
"domain_registry/java/google/registry/ui/css/registrar_dbg.css": "assets/css/registrar_dbg.css",
@ -77,7 +74,6 @@ java_library(
srcs = glob(["*.java"]),
visibility = ["//visibility:public"],
deps = [
"//java/google/registry/config",
"@com_google_appengine_api_1_0_sdk",
"@com_google_code_findbugs_jsr305",
"@com_google_dagger",
@ -124,16 +120,3 @@ closure_js_binary(
"//java/google/registry/ui/js/registrar",
],
)
################################################################################
## Braintree Payment Method Frame (Brainframe)
closure_js_binary(
name = "brain_bin",
entry_points = ["goog:registry.registrar.BrainFrame.main"],
output_wrapper = "%output%//# sourceMappingURL=brain_bin.js.map",
deps = [
"//java/google/registry/ui/externs",
"//java/google/registry/ui/js/registrar",
],
)

View file

@ -1,53 +0,0 @@
// Copyright 2017 The Nomulus 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 google.registry.ui;
import com.google.appengine.api.users.UserService;
import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
/** Dagger module for UI configuration. */
@Module
public final class ConsoleConfigModule { // TODO(b/26829015): Move to config package.
@Provides
static ConsoleDebug provideConsoleDebug() {
return ConsoleDebug.get();
}
/** URL of Braintree iframe sandbox iframe static HTML file. */
@Provides
@Config("brainframe")
static String provideBrainframe(
ConsoleDebug debug,
UserService userService,
@Config("projectId") String projectId) {
switch (debug) {
case PRODUCTION:
return String.format("https://%s.storage.googleapis.com/%s",
projectId,
userService.isUserLoggedIn() && userService.isUserAdmin()
? "brainframe-map.html"
: "brainframe.html");
case DEBUG:
case RAW:
case TEST:
return "/assets/html/insecure-brainframe.html";
default:
throw new AssertionError(debug.toString());
}
}
}

View file

@ -14,6 +14,9 @@
package google.registry.ui;
import dagger.Module;
import dagger.Provides;
/** Enum defining which JS/CSS files get rendered in a soy templates. */
public enum ConsoleDebug {
@ -44,4 +47,14 @@ public enum ConsoleDebug {
public static void set(ConsoleDebug value) {
System.setProperty(PROPERTY, value.toString());
}
/** Dagger module for ConsoleDebug. */
@Module
public static final class ConsoleConfigModule {
@Provides
static ConsoleDebug provideConsoleDebug() {
return ConsoleDebug.get();
}
}
}

View file

@ -1,8 +0,0 @@
<!doctype html>
<!-- gsutil cp -a public-read -z html brainframe-alpha.html gs://domain-registry-alpha/brainframe.html -->
<!-- curl https://domain-registry-alpha.storage.googleapis.com/brainframe.html -->
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
<script src="https://domain-registry-alpha.appspot.com/assets/js/brain_bin.js"></script>
<body style="margin:0">
<form><div id="brainframe"></div></form>
<script>registry.registrar.BrainFrame.main('https://domain-registry-alpha.appspot.com', 'brainframe');</script>

View file

@ -1,14 +0,0 @@
<!doctype html>
<!-- Production iframe sandbox for Braintree iframe -->
<!-- -->
<!-- In order to securely embed Braintree's iframe, this iframe must be -->
<!-- served from a separate origin. To do this, we manually deploy this -->
<!-- as a static HTML file to a cloud storage bucket, as follows: -->
<!-- -->
<!-- gsutil cp -a public-read -z html brainframe.html gs://domain-registry/brainframe.html -->
<!-- curl https://domain-registry.storage.googleapis.com/brainframe.html -->
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
<script src="https://domain-registry.appspot.com/assets/js/brain_bin.js"></script>
<body style="margin:0">
<form><div id="brainframe"></div></form>
<script>registry.registrar.BrainFrame.main('https://domain-registry.appspot.com', 'brainframe');</script>

View file

@ -1,11 +0,0 @@
<!doctype html>
<!-- This can only be used by admins in a testing environment. -->
<!-- This iframe provides no isolation of Braintree from Console. -->
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
<script>var CLOSURE_NO_DEPS = true;</script>
<script src="/assets/sources/closure_library/closure/goog/base.js"></script>
<script src="/assets/sources/domain_registry/java/google/registry/ui/deps.js"></script>
<body style="margin:0">
<form><div id="brainframe"></div></form>
<script>goog.require('registry.registrar.BrainFrame.main');</script>
<script>registry.registrar.BrainFrame.main('*', 'brainframe');</script>

View file

@ -1,4 +0,0 @@
<!doctype html>
<!-- Brainframe for integration testing. -->
<form><div id="brainframe"></div></form>
<script>window.parent.postMessage('{"type": "payment_method", "method": {"type": "CreditCard", "nonce": "imanonce", "details": {"cardType": "Visa", "lastTwo": "00"}}}', '*');</script>

View file

@ -75,28 +75,3 @@ div.domain-registrar-contact div.tooltip .pointer {
padding-left: 1em;
list-style: disc inside;
}
.reg-payment p {
max-width: 45em;
}
/* Note: Empty definitions are for class name minimization. */
.reg-payment-form {}
.reg-payment-form-method {}
.reg-payment-form-method.kd-formerror {
border: none;
}
.reg-payment-form-method-info {}
.reg-payment-form-submit {}
.reg-payment-form-loader {
vertical-align: middle;
padding-left: 10px;
}
.reg-payment-again {}

View file

@ -1,456 +0,0 @@
// Copyright 2017 The Nomulus 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.
/**
* @fileoverview Braintree JS SDK v2 externs. This file tells the Closure
* Compiler how Braintree's API is defined, which allows us to use it with type
* safety and dot-notation.
* @externs
*/
/**
* @type {!braintreepayments.Braintree}
*/
var braintree;
/**
* Fake namespace for pure Closure Compiler types not defined by the SDK.
*/
var braintreepayments = {};
/**
* @constructor
* @final
*/
braintreepayments.Braintree = function() {};
/**
* @param {string} clientTokenFromServer
* @param {string} integrationType Either 'dropin' or 'custom'.
* @param {{container: (string|!Element|undefined),
* dataCollector: (!Object|undefined),
* enableCORS: (boolean|undefined),
* form: (string|undefined),
* hostedFields: (!Object|undefined),
* id: (string|undefined),
* onError: (function(!braintreepayments.Error)|undefined),
* onPaymentMethodReceived:
* (function(!braintreepayments.PaymentMethod)|undefined),
* onReady: (function(!braintreepayments.Integrator)|undefined),
* paypal: (undefined|{
* amount: (number|undefined),
* container: (string|!Element),
* currency: (string|undefined),
* displayName: (string|undefined),
* enableBillingAddress: (boolean|undefined),
* enableShippingAddress: (boolean|undefined),
* headless: (boolean|undefined),
* locale: (string|undefined),
* onCancelled: (function()|undefined),
* onPaymentMethodReceived:
* (function(!braintreepayments.PaymentMethod)|undefined),
* onUnsupported: (function()|undefined),
* paymentMethodNonceInputField: (string|!Element|undefined),
* shippingAddressOverride: (undefined|{
* recipientName: string,
* streetAddress: string,
* extendedAddress: (string|undefined),
* locality: string,
* countryCodeAlpha2: string,
* postalCode: string,
* region: string,
* phone: (string|undefined),
* editable: boolean
* }),
* singleUse: (boolean|undefined)
* })
* }} options
* @see https://developers.braintreepayments.com/guides/client-sdk/javascript/v2#global-setup
*/
braintreepayments.Braintree.prototype.setup =
function(clientTokenFromServer, integrationType, options) {};
/**
* @constructor
* @final
* @see https://developers.braintreepayments.com/guides/drop-in/javascript/v2#onerror
*/
braintreepayments.Error = function() {};
/**
* Describes type of error that occurred (e.g. "CONFIGURATION", "VALIDATION".)
* @type {string}
* @const
*/
braintreepayments.Error.prototype.type;
/**
* Human-readable string describing the error.
* @type {string}
* @const
*/
braintreepayments.Error.prototype.message;
/**
* @type {(!braintreepayments.ErrorDetails|undefined)}
* @const
*/
braintreepayments.Error.prototype.details;
/**
* @constructor
* @final
*/
braintreepayments.ErrorDetails = function() {};
/**
* @type {(!Array<!braintreepayments.ErrorField>|undefined)}
* @const
*/
braintreepayments.ErrorDetails.prototype.invalidFields;
/**
* @constructor
* @final
*/
braintreepayments.ErrorField = function() {};
/**
* Field which failed validation. It will match one of the following: "number",
* "cvv", "expiration", or "postalCode".
* @type {string}
* @const
*/
braintreepayments.ErrorField.prototype.fieldKey;
/**
* This will be `true` if the associated input is empty.
* @type {(boolean|undefined)}
* @const
*/
braintreepayments.ErrorField.prototype.isEmpty;
/**
* @constructor
* @final
*/
braintreepayments.Integrator = function() {};
/**
* @type {string}
* @const
*/
braintreepayments.Integrator.prototype.deviceData;
/**
* @type {!braintreepayments.PaypalIntegrator}
* @const
*/
braintreepayments.Integrator.prototype.paypal;
/**
* @param {function()=} opt_callback
* @see https://developers.braintreepayments.com/guides/client-sdk/javascript/v2#teardown
*/
braintreepayments.Integrator.prototype.teardown = function(opt_callback) {};
/**
* @constructor
* @final
*/
braintreepayments.PaypalIntegrator = function() {};
/**
* @return {void}
*/
braintreepayments.PaypalIntegrator.prototype.closeAuthFlow = function() {};
/**
* @return {void}
*/
braintreepayments.PaypalIntegrator.prototype.initAuthFlow = function() {};
/**
* @constructor
* @final
*/
braintreepayments.PaymentMethod = function() {};
/**
* @type {string}
* @const
*/
braintreepayments.PaymentMethod.prototype.nonce;
/**
* Either 'CreditCard' or 'PayPalAccount'.
* @type {string}
* @const
*/
braintreepayments.PaymentMethod.prototype.type;
/**
* @type {(!braintreepayments.PaymentMethodDetailsCard|
* !braintreepayments.PaymentMethodDetailsPaypal)}
* @const
*/
braintreepayments.PaymentMethod.prototype.details;
/**
* @constructor
* @final
* @see https://developers.braintreepayments.com/guides/client-sdk/javascript/v2#payment-method-details
*/
braintreepayments.PaymentMethodDetailsCard = function() {};
/**
* Can be 'Visa', 'MasterCard', 'Discover', 'Amex', or 'JCB'.
* @type {string}
* @const
*/
braintreepayments.PaymentMethodDetailsCard.prototype.cardType;
/**
* @type {string}
* @const
*/
braintreepayments.PaymentMethodDetailsCard.prototype.lastTwo;
/**
* @constructor
* @final
* @see https://developers.braintreepayments.com/guides/paypal/client-side/javascript/v2#options
*/
braintreepayments.PaymentMethodDetailsPaypal = function() {};
/**
* @type {string}
* @const
*/
braintreepayments.PaymentMethodDetailsPaypal.prototype.email;
/**
* @type {string}
* @const
*/
braintreepayments.PaymentMethodDetailsPaypal.prototype.firstName;
/**
* @type {string}
* @const
*/
braintreepayments.PaymentMethodDetailsPaypal.prototype.lastName;
/**
* @type {string}
* @const
*/
braintreepayments.PaymentMethodDetailsPaypal.prototype.phone;
/**
* @type {string}
* @const
*/
braintreepayments.PaymentMethodDetailsPaypal.prototype.payerID;
/**
* @type {!braintreepayments.PaypalShippingAddress}
* @const
*/
braintreepayments.PaymentMethodDetailsPaypal.prototype.shippingAddress;
/**
* @type {(!braintreepayments.PaypalBillingAddress|undefined)}
* @const
*/
braintreepayments.PaymentMethodDetailsPaypal.prototype.billingAddress;
/**
* @constructor
* @final
*/
braintreepayments.PaypalShippingAddress = function() {};
/**
* @type {number}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.id;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.type;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.recipientName;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.streetAddress;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.extendedAddress;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.locality;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.region;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.postalCode;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.countryCodeAlpha;
/**
* @type {boolean}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.defaultAddress;
/**
* @type {boolean}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.preferredAddress;
/**
* @constructor
* @final
*/
braintreepayments.PaypalBillingAddress = function() {};
/**
* @type {string}
* @const
*/
braintreepayments.PaypalBillingAddress.prototype.streetAddress;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalBillingAddress.prototype.extendedAddress;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalBillingAddress.prototype.locality;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalBillingAddress.prototype.region;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalBillingAddress.prototype.postalCode;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalBillingAddress.prototype.countryCodeAlpha2;

View file

@ -1,66 +0,0 @@
// Copyright 2017 The Nomulus 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.
/**
* @fileoverview RegistrarPaymentAction JSON RPC definitions.
* @externs
*/
/**
* @suppress {duplicate}
*/
var registry = {};
/**
* @suppress {duplicate}
*/
registry.rpc = {};
registry.rpc.Payment = {};
/**
* @typedef {{
* currency: string,
* amount: string,
* paymentMethodNonce: string
* }}
*/
registry.rpc.Payment.Request;
/**
* @typedef {registry.json.Response.<!registry.rpc.Payment.Result>}
*/
registry.rpc.Payment.Response;
/**
* @constructor
*/
registry.rpc.Payment.Result = function() {};
/**
* @type {string}
*/
registry.rpc.Payment.Result.prototype.id;
/**
* @type {string}
*/
registry.rpc.Payment.Result.prototype.formattedAmount;

View file

@ -1,68 +0,0 @@
// Copyright 2017 The Nomulus 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.
/**
* @fileoverview RegistrarPaymentSetupAction JSON RPC definitions.
* @externs
*/
/**
* @suppress {duplicate}
*/
var registry = {};
/**
* @suppress {duplicate}
*/
registry.rpc = {};
registry.rpc.PaymentSetup = {};
/**
* @typedef {Object}
*/
registry.rpc.PaymentSetup.Request;
/**
* @typedef {registry.json.Response.<!registry.rpc.PaymentSetup.Result>}
*/
registry.rpc.PaymentSetup.Response;
/**
* @constructor
*/
registry.rpc.PaymentSetup.Result = function() {};
/**
* @type {string}
*/
registry.rpc.PaymentSetup.Result.prototype.token;
/**
* @type {string}
*/
registry.rpc.PaymentSetup.Result.prototype.brainframe;
/**
* @type {!Array.<string>}
*/
registry.rpc.PaymentSetup.Result.prototype.currencies;

View file

@ -1,267 +0,0 @@
// Copyright 2017 The Nomulus 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.
goog.provide('registry.registrar.BrainFrame');
goog.provide('registry.registrar.BrainFrame.main');
goog.require('goog.Timer');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventType');
goog.require('goog.json');
goog.require('goog.object');
goog.require('goog.style');
goog.forwardDeclare('goog.events.BrowserEvent');
/**
* Sandboxed iframe for Braintree JS SDK v2 iframe.
*
* <p>This class adds an additional layer of security between the Registrar
* Console and JavaScript loaded from Braintree's web server.
*
* <p>The main function for this class is compiled into a separate binary,
* which is loaded within an iframe that's hosted on a different domain than
* the production environment. This ensures that cross origin browser security
* policies take effect.
*
* @param {string} origin FQDN of production environment.
* @param {string} containerId ID of Braintree container element.
* @constructor
* @extends {goog.events.EventHandler}
* @final
*/
registry.registrar.BrainFrame = function(origin, containerId) {
registry.registrar.BrainFrame.base(this, 'constructor');
/**
* Hostname of production registry, e.g. domain-registry.appspot.com.
* @private {string}
* @const
*/
this.origin_ = origin;
/**
* Div that wraps Braintree iframe.
* @private {!Element}
* @const
*/
this.container_ = goog.dom.getRequiredElement(containerId);
/**
* Last known height of `container_`.
* @private {number}
*/
this.containerHeight_ = 0;
/**
* Timer polling for changes in Braintree iframe height.
* @private {!goog.Timer}
* @const
*/
this.resizeTimer_ = new goog.Timer(1000 / 30);
this.registerDisposable(this.resizeTimer_);
this.listen(this.resizeTimer_, goog.Timer.TICK, this.onResizeTimer_);
/**
* Form that wraps `container_`.
* @private {?Element}
* @const
*/
this.form_ = goog.dom.getAncestorByTagNameAndClass(this.container_,
goog.dom.TagName.FORM);
goog.asserts.assert(this.form_ != null);
/**
* State indicating if we're submitting at behest of parent.
* @private {boolean}
*/
this.isSubmitting_ = false;
this.listen(goog.global.window,
goog.events.EventType.MESSAGE,
this.onMessage_);
};
goog.inherits(registry.registrar.BrainFrame, goog.events.EventHandler);
/**
* Runs Braintree sandbox environment.
*/
registry.registrar.BrainFrame.prototype.run = function() {
this.send_(
'type', registry.registrar.BrainFrame.MessageType.TOKEN_REQUEST);
};
/**
* Handles message from parent iframe which sends Braintree token.
* @param {!goog.events.BrowserEvent} e
* @private
*/
registry.registrar.BrainFrame.prototype.onMessage_ = function(e) {
var msg = /** @type {!MessageEvent.<string>} */ (e.getBrowserEvent());
if (msg.source != goog.global.window.parent) {
return;
}
if (this.origin_ != '*' && this.origin_ != msg.origin) {
throw new Error(
'Message origin is "' + msg.origin + '" but wanted: ' + this.origin_);
}
var data = /** @type {!Object} */ (JSON.parse(msg.data));
switch (goog.object.get(data, 'type')) {
case registry.registrar.BrainFrame.MessageType.TOKEN_RESPONSE:
goog.global.braintree.setup(goog.object.get(data, 'token'), 'dropin', {
container: this.container_,
onPaymentMethodReceived: goog.bind(this.onPaymentMethod_, this),
onReady: goog.bind(this.onReady_, this),
onError: goog.bind(this.onError_, this)
});
this.resizeTimer_.start();
break;
case registry.registrar.BrainFrame.MessageType.SUBMIT_REQUEST:
this.isSubmitting_ = true;
// Trigger Braintree JS SDK submit event listener. It does not appear to
// be possible to do this using the Closure Library. This is IE 9+ only.
this.form_.dispatchEvent(new Event(goog.events.EventType.SUBMIT));
break;
default:
throw Error('Unexpected message: ' + msg.data);
}
};
/**
* Polls for resizes of Braintree iframe and propagates them to the parent
* frame which will then use it to resize this iframe.
* @private
*/
registry.registrar.BrainFrame.prototype.onResizeTimer_ = function() {
var height = goog.style.getSize(this.container_).height;
if (height != this.containerHeight_) {
this.send_(
'type', registry.registrar.BrainFrame.MessageType.RESIZE_REQUEST,
'height', height);
this.containerHeight_ = height;
}
};
/**
* Callback Braintree iframe has fully loaded.
* @private
*/
registry.registrar.BrainFrame.prototype.onReady_ = function() {
this.send_('type', registry.registrar.BrainFrame.MessageType.READY);
};
/**
* Callback Braintree says an error happened.
* @param {!braintreepayments.Error} error
* @private
*/
registry.registrar.BrainFrame.prototype.onError_ = function(error) {
this.isSubmitting_ = false;
this.send_('type', registry.registrar.BrainFrame.MessageType.SUBMIT_ERROR,
'message', error.message);
};
/**
* Callback when user successfully gave Braintree payment details.
* @param {!braintreepayments.PaymentMethod} pm
* @private
*/
registry.registrar.BrainFrame.prototype.onPaymentMethod_ = function(pm) {
// TODO(b/26829319): The Braintree JS SDK does not seem to recognize the
// enter key while embedded inside our sandbox iframe. So
// at this time, this callback will only be invoked after
// we've submitted the form manually at the behest of
// payment.js, which means isSubmitting_ will be true.
this.send_(
'type', registry.registrar.BrainFrame.MessageType.PAYMENT_METHOD,
'submit', this.isSubmitting_,
'method', pm);
this.isSubmitting_ = false;
};
/**
* Sends message to parent iframe.
* @param {...*} var_args Passed along to `goog.object.create`.
* @private
*/
registry.registrar.BrainFrame.prototype.send_ = function(var_args) {
goog.asserts.assert(arguments[0] == 'type');
registry.registrar.BrainFrame.postMessage_(
goog.json.serialize(goog.object.create.apply(null, arguments)),
this.origin_);
};
/**
* Delegates to `window.parent.postMessage`. This method exists because
* IE will not allow us to mock methods on the window object.
* @param {string} message
* @param {string} origin
* @private
*/
registry.registrar.BrainFrame.postMessage_ = function(message, origin) {
goog.global.window.parent.postMessage(message, origin);
};
/**
* Message types passed between brainframe and payment page.
* @enum {string}
*/
registry.registrar.BrainFrame.MessageType = {
/** Brainframe asks payment page for Braintree token. */
TOKEN_REQUEST: 'token_request',
/** Payment page sends brainframe Braintree token. */
TOKEN_RESPONSE: 'token_response',
/** Brainframe asks payment page to be resized. */
RESIZE_REQUEST: 'resize_request',
/** Brainframe tells payment page it finished loading. */
READY: 'ready',
/** Payment page asks brainframe to submit Braintree payment method form. */
SUBMIT_REQUEST: 'submit_request',
/** Brainframe tells payment page it failed to submit. */
SUBMIT_ERROR: 'submit_error',
/** Brainframe gives payment method info and nonce to payment page. */
PAYMENT_METHOD: 'payment_method'
};
/**
* Entrypoint for {@link registry.registrar.BrainFrame}.
* @param {string} origin
* @param {string} containerId
* @export
*/
registry.registrar.BrainFrame.main = function(origin, containerId) {
new registry.registrar.BrainFrame(origin, containerId).run();
};

View file

@ -26,7 +26,6 @@ goog.require('registry.registrar.Dashboard');
goog.require('registry.registrar.Domain');
goog.require('registry.registrar.EppSession');
goog.require('registry.registrar.Host');
goog.require('registry.registrar.Payment');
goog.require('registry.registrar.Resources');
goog.require('registry.registrar.SecuritySettings');
goog.require('registry.registrar.WhoisSettings');
@ -86,7 +85,6 @@ registry.registrar.Console = function(params) {
this.pageMap['contact-us'] = registry.registrar.ContactUs;
this.pageMap['resources'] = registry.registrar.Resources;
this.pageMap['contact'] = registry.registrar.Contact;
this.pageMap['payment'] = registry.registrar.Payment;
this.pageMap['domain'] = registry.registrar.Domain;
this.pageMap['host'] = registry.registrar.Host;
this.pageMap[''] = registry.registrar.Dashboard;

View file

@ -1,415 +0,0 @@
// Copyright 2017 The Nomulus 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.
goog.provide('registry.registrar.Payment');
goog.require('goog.Uri');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.events.EventType');
goog.require('goog.json');
goog.require('goog.object');
goog.require('goog.soy');
goog.require('registry.Component');
goog.require('registry.MenuButton');
goog.require('registry.Session');
goog.require('registry.forms');
goog.require('registry.registrar.BrainFrame');
goog.require('registry.soy.registrar.console');
goog.require('registry.soy.registrar.payment');
goog.require('registry.util');
goog.forwardDeclare('goog.events.BrowserEvent');
goog.forwardDeclare('registry.registrar.Console');
/**
* Page allowing registrar to send money to registry.
*
* <p>This page contains a form that asks the user to enter an arbitrary amount
* and a payment method, which can be credit card or PayPal. Multiple
* currencies are supported.
*
* <h3>PCI Compliance</h3>
*
* <p>We don't have any access whatsoever to the credit card information. We
* embed an iframe run by Braintree Payments. The user can then provide his
* credit card (or PayPal) details directly to Braintree. Braintree then gives
* us a nonce value representing the payment method, which we can use to issue
* the transaction.
*
* <h3>Bidirectional Protection</h3>
*
* <p>To use Braintree's iframe, we need to load a script from their server. We
* don't want that script to have access to the Registrar Console. If Braintree
* got pwnd, the attacker would be able to issue EPP commands as a registrar.
*
* <p>We fix this problem by embedding the Braintree iframe inside another
* sandbox iframe that's hosted from a Cloud Storage bucket. This frame is
* defined by `brainframe.html`. It's basically an empty shell that sends
* a request back to the production environment for `brainframe.js`.
*
* <p>The importance of the Cloud Storage bucket is that it is served from a
* separate domain. This causes the browser to forbid the iframe from accessing
* the contents of the parent frame. The HTML5 `sandbox` attribute does
* this too, but we can't use it, because the Venmo functionality in the
* Braintree JS SDK needs to be able to access `document.cookie`, which
* is forbidden in a sandbox environment. This HTML5 `sandbox` feature is
* also not available in older versions of Internet Explorer.
*
* <h3>Business Logic</h3>
*
* <p>This page starts off as a loading glyph, while we issue an RPC to the
* backend. We ask for a Braintree token, which currencies are available, and
* the location of the brainframe HTML file. Once we get that data, we render
* the form.
*
* <p>Once the sandbox iframe inside that form has loaded, it'll send us a
* message asking for the token. We give it the token, which it uses to load
* the Braintree iframe.
*
* <p>To make sure the sandbox iframe is the same size as the Braintree iframe,
* the sandbox iframe will send us messages on occasion asking to be resized.
*
* <p>The disabled state of the submit button is managed judiciously. It's
* disabled initially, until we get a READY message from the sandbox iframe,
* indicating that the Braintree iframe is fully loaded. We also disable the
* submit button during the submit process.
*
* <p>When the user presses the submit button, we send a message to the sandbox
* iframe asking it to submit the Braintree iframe. When the Braintree iframe
* is submitted, it gives the sandbox iframe the the payment method nonce,
* which it passes along to us. Then we pass the form data to the backend via
* the payment RPC, which invokes the Braintree Java API to issue the
* transaction.
*
* <p>If the payment RPC fails, we'll either show an error on a field, or in a
* bloody butterbar. If it succeeds, then the backend will give us the
* transaction ID assigned by Braintree, which we then render on a success
* page.
*
* <p>The success page contains a "Make Another Payment" button which, if
* clicked, will reset the state of this page back to the beginning.
*
* @param {!registry.registrar.Console} console
* @param {string} xsrfToken Security token to pass back to the server.
* @constructor
* @extends {registry.Component}
* @final
*/
registry.registrar.Payment = function(console, xsrfToken) {
registry.registrar.Payment.base(this, 'constructor', console);
/**
* Element in which this page is rendered.
* @private {!Element}
* @const
*/
this.content_ = goog.dom.getRequiredElement('reg-content');
/**
* Braintree API nonce token generated by the backend. This value is a
* prerequisite to rendering the Braintree iframe.
* @private {string}
*/
this.token_ = '';
/**
* Braintree API nonce value for payment method selected by user.
* @private {string}
*/
this.paymentMethodNonce_ = '';
/**
* Currency drop-down widget in form.
* @private {?registry.MenuButton}
*/
this.currencyMenu_ = null;
/**
* XHR client to `RegistrarPaymentSetupAction`.
* @private {!registry.Session.<!registry.rpc.PaymentSetup.Request,
* !registry.rpc.PaymentSetup.Response>}
* @const
*/
this.setupRpc_ =
new registry.Session(new goog.Uri('/registrar-payment-setup'),
xsrfToken,
registry.Session.ContentType.JSON);
/**
* XHR client to `RegistrarPaymentAction`.
* @private {!registry.Session.<!registry.rpc.Payment.Request,
* !registry.rpc.Payment.Response>}
* @const
*/
this.paymentRpc_ =
new registry.Session(new goog.Uri('/registrar-payment'),
xsrfToken,
registry.Session.ContentType.JSON);
this.listen(goog.global.window,
goog.events.EventType.MESSAGE,
this.onMessage_);
};
goog.inherits(registry.registrar.Payment, registry.Component);
/** @override */
registry.registrar.Payment.prototype.bindToDom = function(id) {
registry.registrar.Payment.base(this, 'bindToDom', id);
if (!goog.isNull(goog.dom.getElement('reg-app-buttons'))) {
goog.dom.removeChildren(goog.dom.getElement('reg-app-buttons'));
}
if (!registry.registrar.Payment.isBrowserSupported_()) {
goog.soy.renderElement(this.content_,
registry.soy.registrar.payment.unsupported);
return;
}
goog.soy.renderElement(this.content_, registry.soy.registrar.console.loading);
this.setupRpc_.sendXhrIo({}, goog.bind(this.onSetup_, this));
};
/**
* Handler invoked when we receive information from our backend, such as a
* Braintree token, which is necessary for us to render the payment form.
* @param {!registry.rpc.PaymentSetup.Response} response
* @private
*/
registry.registrar.Payment.prototype.onSetup_ = function(response) {
if (response.status != 'SUCCESS') {
if (response.message == 'not-using-cc-billing') {
goog.soy.renderElement(this.content_,
registry.soy.registrar.payment.notUsingCcBilling);
} else {
registry.forms.displayError(response.message);
}
return;
}
var result = response.results[0];
this.token_ = result.token;
this.paymentMethodNonce_ = '';
goog.soy.renderElement(this.content_,
registry.soy.registrar.payment.form,
result);
this.listen(
goog.dom.getRequiredElementByClass(goog.getCssName('reg-payment-form')),
goog.events.EventType.SUBMIT,
this.onSubmit_);
this.currencyMenu_ =
new registry.MenuButton(goog.dom.getRequiredElement('currency'));
this.registerDisposable(this.currencyMenu_);
};
/**
* Handler invoked when payment form is submitted.
* @param {!goog.events.BrowserEvent} e
* @private
*/
registry.registrar.Payment.prototype.onSubmit_ = function(e) {
e.preventDefault();
this.submit_();
};
/**
* Submits payment form.
* @private
*/
registry.registrar.Payment.prototype.submit_ = function() {
registry.forms.resetErrors();
registry.registrar.Payment.setEnabled_(false);
if (this.paymentMethodNonce_ == '') {
this.send_(
'type', registry.registrar.BrainFrame.MessageType.SUBMIT_REQUEST);
return;
}
this.paymentRpc_.sendXhrIo(
{
amount: goog.dom.getRequiredElement('amount').value,
currency: this.currencyMenu_.getValue(),
paymentMethodNonce: this.paymentMethodNonce_
},
goog.bind(this.onPayment_, this));
};
/**
* Callback for backend payment RPC that issues the transaction.
* @param {!registry.rpc.Payment.Response} response
* @private
*/
registry.registrar.Payment.prototype.onPayment_ = function(response) {
registry.registrar.Payment.setEnabled_(true);
if (response.status != 'SUCCESS') {
registry.forms.displayError(response.message, response.field);
return;
}
goog.soy.renderElement(this.content_,
registry.soy.registrar.payment.success,
response.results[0]);
this.listenOnce(
goog.dom.getRequiredElementByClass(goog.getCssName('reg-payment-again')),
goog.events.EventType.CLICK,
this.bindToDom);
};
/**
* Handler invoked when `brainframe.js` sends us a message.
* @param {!goog.events.BrowserEvent} e
* @private
*/
registry.registrar.Payment.prototype.onMessage_ = function(e) {
var msg = /** @type {!MessageEvent.<string>} */ (e.getBrowserEvent());
var brainframe =
goog.dom.getElementByClass(goog.getCssName('reg-payment-form-method'));
if (brainframe == null ||
msg.source != goog.dom.getFrameContentWindow(brainframe)) {
return;
}
var data;
try {
data = /** @type {!Object} */ (JSON.parse(msg.data));
} catch (ex) {
// TODO(b/26876003): Figure out why it's possible that the Braintree iframe
// is able to propagate messages up to our level.
registry.util.log(ex, msg.source, msg.data);
return;
}
switch (goog.object.get(data, 'type')) {
case registry.registrar.BrainFrame.MessageType.TOKEN_REQUEST:
goog.asserts.assert(this.token_ != '');
this.send_(
'type', registry.registrar.BrainFrame.MessageType.TOKEN_RESPONSE,
'token', this.token_);
break;
case registry.registrar.BrainFrame.MessageType.RESIZE_REQUEST:
brainframe.height = goog.object.get(data, 'height');
break;
case registry.registrar.BrainFrame.MessageType.READY:
registry.registrar.Payment.setEnabled_(true);
break;
case registry.registrar.BrainFrame.MessageType.SUBMIT_ERROR:
registry.registrar.Payment.setEnabled_(true);
registry.forms.displayError(goog.object.get(data, 'message'), 'method');
break;
case registry.registrar.BrainFrame.MessageType.PAYMENT_METHOD:
registry.registrar.Payment.setEnabled_(true);
this.setPaymentMethod_(
/** @type {!braintreepayments.PaymentMethod} */ (
goog.object.get(data, 'method')));
if (goog.object.get(data, 'submit')) {
this.submit_();
}
break;
default:
throw Error('Unexpected message: ' + msg.data);
}
};
/**
* Updates UI to display selected payment method.
*
* <p>We remove the iframe from the page as soon as this happens, because the
* UI would be busted otherwise. The Braintree UI for changing the payment
* method (after it's been entered) does not appear to stop respond to submit
* events. It also causes ugly scroll bars to appear inside the iframe.
*
* <p>This approach is also advantageous for screenshot testing. We do not want
* our continuous integration testing system to talk to Braintree's servers. So
* we mock out the brainframe with `integration-test-brainframe.html`
* which <i>only</i> sends us a METHOD message, which we then render ourselves.
*
* @param {!braintreepayments.PaymentMethod} pm
* @private
*/
registry.registrar.Payment.prototype.setPaymentMethod_ = function(pm) {
registry.forms.resetErrors();
goog.dom.removeNode(
goog.dom.getElementByClass(goog.getCssName('reg-payment-form-method')));
var paymentMethodInfoBox =
goog.dom.getRequiredElementByClass(
goog.getCssName('reg-payment-form-method-info'));
switch (pm.type) {
case 'CreditCard':
goog.soy.renderElement(
paymentMethodInfoBox,
registry.soy.registrar.payment.methodInfoCard,
{cardType: pm.details.cardType, lastTwo: pm.details.lastTwo});
break;
case 'PayPalAccount':
goog.soy.renderElement(
paymentMethodInfoBox,
registry.soy.registrar.payment.methodInfoPaypal,
{email: pm.details.email});
break;
default:
throw Error('Unknown payment method: ' + pm.type);
}
registry.util.setVisible(paymentMethodInfoBox, true);
this.paymentMethodNonce_ = pm.nonce;
};
/**
* Sends message to brainframe.
* @param {...*} var_args Passed along to `goog.object.create`.
* @private
*/
registry.registrar.Payment.prototype.send_ = function(var_args) {
goog.asserts.assert(arguments[0] == 'type');
var brainframeWindow =
goog.dom.getFrameContentWindow(
goog.dom.getRequiredElementByClass(
goog.getCssName('reg-payment-form-method')));
// We send a string value to support older versions of IE.
brainframeWindow.postMessage(
goog.json.serialize(goog.object.create.apply(null, arguments)),
'*');
};
/**
* Enables submit button and hides mini loading glyph.
* @param {boolean} enabled
* @private
*/
registry.registrar.Payment.setEnabled_ = function(enabled) {
registry.forms.setEnabled(
goog.dom.getRequiredElementByClass(
goog.getCssName('reg-payment-form-submit')), enabled);
registry.util.setVisible(
goog.dom.getRequiredElementByClass(
goog.getCssName('reg-payment-form-loader')), !enabled);
};
/**
* Returns `true` if browser has all the features we need.
* @return {boolean}
* @private
* @see "http://caniuse.com/#feat=dispatchevent"
*/
registry.registrar.Payment.isBrowserSupported_ = function() {
// dispatchEvent is used by brainframe.js and is IE 9+.
return goog.object.containsKey(
goog.dom.createElement(goog.dom.TagName.FORM),
'dispatchEvent');
};

View file

@ -12,7 +12,6 @@ java_library(
"//java/google/registry/ui/css:registrar_dbg.css.js",
],
deps = [
"//java/google/registry/braintree",
"//java/google/registry/config",
"//java/google/registry/export/sheet",
"//java/google/registry/flows",

View file

@ -137,7 +137,6 @@ public final class ConsoleUiAction implements Runnable {
Registrar.loadByClientIdCached(clientId), "Registrar %s does not exist", clientId);
data.put("xsrfToken", xsrfTokenManager.generateToken(userAuthInfo.user().getEmail()));
data.put("clientId", clientId);
data.put("showPaymentLink", registrar.getBillingMethod() == Registrar.BillingMethod.BRAINTREE);
data.put("requireFeeExtension", registrar.getPremiumPriceAckRequired());
String payload = TOFU_SUPPLIER.get()

View file

@ -1,350 +0,0 @@
// Copyright 2017 The Nomulus 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 google.registry.ui.server.registrar;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Verify.verify;
import static google.registry.security.JsonResponseHelper.Status.ERROR;
import static google.registry.security.JsonResponseHelper.Status.SUCCESS;
import static java.util.Arrays.asList;
import com.braintreegateway.BraintreeGateway;
import com.braintreegateway.Result;
import com.braintreegateway.Transaction;
import com.braintreegateway.TransactionRequest;
import com.braintreegateway.ValidationError;
import com.braintreegateway.ValidationErrors;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.re2j.Pattern;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
import google.registry.request.JsonActionRunner;
import google.registry.request.JsonActionRunner.JsonAction;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthResult;
import google.registry.security.JsonResponseHelper;
import google.registry.ui.forms.FormField;
import google.registry.ui.forms.FormFieldException;
import java.math.BigDecimal;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import org.joda.money.CurrencyUnit;
import org.joda.money.IllegalCurrencyException;
import org.joda.money.Money;
/**
* Action handling submission of customer payment form.
*
* <h3>Request Object</h3>
*
* <p>The request payload is a JSON object with the following fields:
*
* <dl>
* <dt>amount
* <dd>String containing a fixed point value representing the amount of money the registrar
* customer wishes to send the registry. This amount is arbitrary and entered manually by the
* customer in the payment form, as there is currently no integration with the billing system.
* <dt>currency
* <dd>String containing a three letter ISO currency code, which is used to look up the Braintree
* merchant account ID to which payment should be posted.
* <dt>paymentMethodNonce
* <dd>UUID nonce string supplied by the Braintree JS SDK representing the selected payment
* method.
* </dl>
*
* <h3>Response Object</h3>
*
* <p>The response payload will be a JSON response object (as defined by {@link JsonResponseHelper})
* which, if successful, will contain a single result object with the following fields:
*
* <dl>
* <dt>id
* <dd>String containing transaction ID returned by Braintree gateway.
* <dt>formattedAmount
* <dd>String containing amount paid, which can be displayed to the customer on a success page.
* </dl>
*
* <p><b>Note:</b> These definitions corresponds to Closure Compiler extern {@code
* registry.rpc.Payment} which must be updated should these definitions change.
*
* <h3>PCI Compliance</h3>
*
* <p>The request object will not contain credit card information, but rather a {@code
* payment_method_nonce} field that's populated by the Braintree JS SDK iframe.
*
* @see RegistrarPaymentSetupAction
*/
@Action(
path = "/registrar-payment",
method = Action.Method.POST,
auth = Auth.AUTH_PUBLIC_LOGGED_IN
)
public final class RegistrarPaymentAction implements Runnable, JsonAction {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final FormField<String, BigDecimal> AMOUNT_FIELD =
FormField.named("amount")
.trimmed()
.emptyToNull()
.required()
.matches(Pattern.compile("-?\\d+(?:\\.\\d+)?"), "Invalid number.")
.transform(
BigDecimal.class,
value -> {
BigDecimal result = new BigDecimal(value);
if (result.signum() != 1) {
throw new FormFieldException("Must be a positive number.");
}
return result;
})
.build();
private static final FormField<String, CurrencyUnit> CURRENCY_FIELD =
FormField.named("currency")
.trimmed()
.emptyToNull()
.required()
.matches(Pattern.compile("[A-Z]{3}"), "Invalid currency code.")
.transform(
CurrencyUnit.class,
value -> {
try {
return CurrencyUnit.of(value);
} catch (IllegalCurrencyException ignored) {
throw new FormFieldException("Unknown ISO currency code.");
}
})
.build();
private static final FormField<String, String> PAYMENT_METHOD_NONCE_FIELD =
FormField.named("paymentMethodNonce")
.trimmed()
.emptyToNull()
.required()
.build();
@Inject HttpServletRequest request;
@Inject BraintreeGateway braintreeGateway;
@Inject JsonActionRunner jsonActionRunner;
@Inject AuthResult authResult;
@Inject SessionUtils sessionUtils;
@Inject @Config("braintreeMerchantAccountIds") ImmutableMap<CurrencyUnit, String> accountIds;
@Inject RegistrarPaymentAction() {}
@Override
public void run() {
jsonActionRunner.run(this);
}
@Override
public Map<String, Object> handleJsonRequest(Map<String, ?> json) {
Registrar registrar = sessionUtils.getRegistrarForAuthResult(request, authResult);
logger.atInfo().log("Processing payment: %s", json);
String paymentMethodNonce;
Money amount;
String merchantAccountId;
try {
paymentMethodNonce = PAYMENT_METHOD_NONCE_FIELD.extractUntyped(json).get();
try {
amount = Money.of(
CURRENCY_FIELD.extractUntyped(json).get(),
AMOUNT_FIELD.extractUntyped(json).get());
} catch (ArithmeticException e) {
// This happens when amount has more precision than the currency allows, e.g. $3.141.
throw new FormFieldException(AMOUNT_FIELD.name(), e.getMessage());
}
merchantAccountId = accountIds.get(amount.getCurrencyUnit());
if (merchantAccountId == null) {
throw new FormFieldException(CURRENCY_FIELD.name(), "Unsupported currency.");
}
} catch (FormFieldException e) {
logger.atWarning().withCause(e).log("Form field error in RegistrarPaymentAction.");
return JsonResponseHelper.createFormFieldError(e.getMessage(), e.getFieldName());
}
Result<Transaction> result =
braintreeGateway.transaction().sale(
new TransactionRequest()
.amount(amount.getAmount())
.paymentMethodNonce(paymentMethodNonce)
.merchantAccountId(merchantAccountId)
.customerId(registrar.getClientId())
.options()
.submitForSettlement(true)
.done());
if (result.isSuccess()) {
return handleSuccessResponse(result.getTarget());
} else if (result.getTransaction() != null) {
Transaction transaction = result.getTransaction();
switch (transaction.getStatus()) {
case PROCESSOR_DECLINED:
return handleProcessorDeclined(transaction);
case SETTLEMENT_DECLINED:
return handleSettlementDecline(transaction);
case GATEWAY_REJECTED:
return handleRejection(transaction);
default:
return handleMiscProcessorError(transaction);
}
} else {
return handleValidationErrorResponse(result.getErrors());
}
}
/**
* Handles a transaction success response.
*
* @see <a href="https://developers.braintreepayments.com/reference/response/transaction/java#success">
* Braintree - Transaction - Success</a>
* @see <a href="https://developers.braintreepayments.com/reference/general/statuses#transaction">
* Braintree - Statuses - Transaction</a>
*/
private Map<String, Object> handleSuccessResponse(Transaction transaction) {
// XXX: Currency scaling: https://github.com/braintree/braintree_java/issues/33
Money amount =
Money.of(CurrencyUnit.of(transaction.getCurrencyIsoCode()),
transaction.getAmount().stripTrailingZeros());
logger.atInfo().log(
"Transaction for %s via %s %s with ID: %s",
amount,
transaction.getPaymentInstrumentType(), // e.g. credit_card, paypal_account
transaction.getStatus(), // e.g. SUBMITTED_FOR_SETTLEMENT
transaction.getId());
return JsonResponseHelper
.create(SUCCESS, "Payment processed successfully", asList(
ImmutableMap.of(
"id", transaction.getId(),
"formattedAmount", formatMoney(amount))));
}
/**
* Handles a processor declined response.
*
* <p>This happens when the customer's bank blocks the transaction.
*
* @see <a href="https://developers.braintreepayments.com/reference/response/transaction/java#processor-declined">
* Braintree - Transaction - Processor declined</a>
* @see <a href="https://articles.braintreepayments.com/control-panel/transactions/declines">
* Braintree - Transactions/Declines</a>
*/
private Map<String, Object> handleProcessorDeclined(Transaction transaction) {
logger.atWarning().log(
"Processor declined: %s %s",
transaction.getProcessorResponseCode(), transaction.getProcessorResponseText());
return JsonResponseHelper.create(ERROR,
"Payment declined: " + transaction.getProcessorResponseText());
}
/**
* Handles a settlement declined response.
*
* <p>This is a very rare condition that, for all intents and purposes, means the same thing as a
* processor declined response.
*
* @see <a href="https://developers.braintreepayments.com/reference/response/transaction/java#processor-settlement-declined">
* Braintree - Transaction - Processor settlement declined</a>
* @see <a href="https://articles.braintreepayments.com/control-panel/transactions/declines">
* Braintree - Transactions/Declines</a>
*/
private Map<String, Object> handleSettlementDecline(Transaction transaction) {
logger.atWarning().log(
"Settlement declined: %s %s",
transaction.getProcessorSettlementResponseCode(),
transaction.getProcessorSettlementResponseText());
return JsonResponseHelper.create(ERROR,
"Payment declined: " + transaction.getProcessorSettlementResponseText());
}
/**
* Handles a gateway rejection response.
*
* <p>This happens when a transaction is blocked due to settings we configured ourselves in the
* Braintree control panel.
*
* @see <a href="https://developers.braintreepayments.com/reference/response/transaction/java#gateway-rejection">
* Braintree - Transaction - Gateway rejection</a>
* @see <a href="https://articles.braintreepayments.com/control-panel/transactions/gateway-rejections">
* Braintree - Transactions/Gateway Rejections</a>
* @see <a href="https://articles.braintreepayments.com/guides/fraud-tools/avs-cvv">
* Braintree - Fruad Tools/Basic Fraud Tools - AVS and CVV rules</a>
* @see <a href="https://articles.braintreepayments.com/guides/fraud-tools/overview">
* Braintree - Fraud Tools/Overview</a>
*/
private Map<String, Object> handleRejection(Transaction transaction) {
logger.atWarning().log("Gateway rejection: %s", transaction.getGatewayRejectionReason());
switch (transaction.getGatewayRejectionReason()) {
case DUPLICATE:
return JsonResponseHelper.create(ERROR, "Payment rejected: Possible duplicate.");
case AVS:
return JsonResponseHelper.create(ERROR, "Payment rejected: Invalid address.");
case CVV:
return JsonResponseHelper.create(ERROR, "Payment rejected: Invalid CVV code.");
case AVS_AND_CVV:
return JsonResponseHelper.create(ERROR, "Payment rejected: Invalid address and CVV code.");
case FRAUD:
return JsonResponseHelper.create(ERROR,
"Our merchant gateway suspects this payment of fraud. Please contact support.");
default:
return JsonResponseHelper.create(ERROR,
"Payment rejected: " + transaction.getGatewayRejectionReason());
}
}
/** Handles a miscellaneous transaction processing error response. */
private Map<String, Object> handleMiscProcessorError(Transaction transaction) {
logger.atWarning().log(
"Error processing transaction: %s %s %s",
transaction.getStatus(),
transaction.getProcessorResponseCode(),
transaction.getProcessorResponseText());
return JsonResponseHelper.create(ERROR,
"Payment failure: "
+ firstNonNull(
emptyToNull(transaction.getProcessorResponseText()),
transaction.getStatus().toString()));
}
/**
* Handles a validation error response from Braintree.
*
* @see <a href="https://developers.braintreepayments.com/reference/response/transaction/java#validation-errors">
* Braintree - Transaction - Validation errors</a>
* @see <a href="https://developers.braintreepayments.com/reference/general/validation-errors/all/java">
* Braintree - Validation Errors/All</a>
*/
private Map<String, Object> handleValidationErrorResponse(ValidationErrors validationErrors) {
List<ValidationError> errors = validationErrors.getAllDeepValidationErrors();
verify(!errors.isEmpty(), "Payment failed but validation error list was empty");
for (ValidationError error : errors) {
logger.atWarning().log(
"Payment validation failed on field: %s\nCode: %s\nMessage: %s",
error.getAttribute(), error.getCode(), error.getMessage());
}
return JsonResponseHelper
.createFormFieldError(errors.get(0).getMessage(), errors.get(0).getAttribute());
}
private static String formatMoney(Money amount) {
String symbol = amount.getCurrencyUnit().getSymbol(Locale.US);
BigDecimal number = amount.getAmount().setScale(amount.getCurrencyUnit().getDecimalPlaces());
return symbol.length() == 1 ? symbol + number : amount.toString();
}
}

View file

@ -1,123 +0,0 @@
// Copyright 2017 The Nomulus 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 google.registry.ui.server.registrar;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.security.JsonResponseHelper.Status.ERROR;
import static google.registry.security.JsonResponseHelper.Status.SUCCESS;
import static java.util.Arrays.asList;
import com.braintreegateway.BraintreeGateway;
import com.google.common.collect.ImmutableMap;
import google.registry.braintree.BraintreeRegistrarSyncer;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
import google.registry.request.JsonActionRunner;
import google.registry.request.JsonActionRunner.JsonAction;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthResult;
import google.registry.security.JsonResponseHelper;
import java.util.Map;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import org.joda.money.CurrencyUnit;
/**
* Action returning information needed to render payment form in browser.
*
* <h3>Request Object</h3>
*
* <p>The request payload must be an empty JSON object.
*
* <h3>Response Object</h3>
*
* <p>The response payload will be a JSON response object (as defined by {@link JsonResponseHelper})
* containing a single result object with the following fields:
*
* <dl>
* <dt>brainframe
* <dd>URL for iframe that loads Braintree payment method selector.
* <dt>token
* <dd>Nonce string obtained from the Braintree API which is needed by the Braintree JS SDK.
* <dt>currencies
* <dd>Array of strings, each containing a three letter currency code, which should be displayed
* to the customer in a drop-down field. This will be all currencies for which a Braintree
* merchant account exists. A currency will even be displayed if no TLD is enabled on the
* customer account that bills in that currency.
* </dl>
*
* <p><b>Note:</b> These definitions corresponds to Closure Compiler extern {@code
* registry.rpc.PaymentSetup} which must be updated should these definitions change.
*
* @see RegistrarPaymentAction
* @see <a
* href="https://developers.braintreepayments.com/start/hello-server/java#generate-a-client-token">Generate
* a client token</a>
*/
@Action(
path = "/registrar-payment-setup",
method = Action.Method.POST,
auth = Auth.AUTH_PUBLIC_LOGGED_IN
)
public final class RegistrarPaymentSetupAction implements Runnable, JsonAction {
@Inject HttpServletRequest request;
@Inject BraintreeGateway braintreeGateway;
@Inject BraintreeRegistrarSyncer customerSyncer;
@Inject JsonActionRunner jsonActionRunner;
@Inject AuthResult authResult;
@Inject SessionUtils sessionUtils;
@Inject @Config("brainframe") String brainframe;
@Inject @Config("braintreeMerchantAccountIds") ImmutableMap<CurrencyUnit, String> accountIds;
@Inject RegistrarPaymentSetupAction() {}
@Override
public void run() {
jsonActionRunner.run(this);
}
@Override
public Map<String, Object> handleJsonRequest(Map<String, ?> json) {
Registrar registrar = sessionUtils.getRegistrarForAuthResult(request, authResult);
if (!json.isEmpty()) {
return JsonResponseHelper.create(ERROR, "JSON request object must be empty");
}
// payment.js is hard-coded to display a specific SOY error template for certain error messages.
if (registrar.getBillingMethod() != Registrar.BillingMethod.BRAINTREE) {
// Registrar needs to contact support to have their billing bit flipped.
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.create(
SUCCESS,
"Success",
asList(
ImmutableMap.of(
"brainframe", brainframe,
"token", braintreeGateway.clientToken().generate(),
"currencies",
accountIds
.keySet()
.stream()
.map(Object::toString)
.collect(toImmutableList()))));
}
}

View file

@ -25,7 +25,6 @@
{@param clientId: string} /** Registrar client identifier. */
{@param username: string} /** Arbitrary username to display. */
{@param logoutUrl: string} /** Generated URL for logging out of Google. */
{@param showPaymentLink: bool}
{@param productName: string} /** Name to display for this software product. */
{@param integrationEmail: string}
{@param supportEmail: string}
@ -76,17 +75,12 @@
/** Sidebar nav. Ids on each elt for testing only. */
{template .navbar_ visibility="private"}
{@param showPaymentLink: bool}
<div id="reg-nav" class="{css('kd-content-sidebar')}">
<ul id="reg-navlist">
<li>
<a href="/registrar#">Home</a>
<li>
<a href="/registrar#resources">Resources &amp; billing</a>
{if $showPaymentLink}
<li>
<a href="/registrar#payment">Pay invoice</a>
{/if}
<li>
<ul>
<span class="{css('reg-navlist-sub')}">Settings</span>

View file

@ -1,141 +0,0 @@
// Copyright 2017 The Nomulus 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.
{namespace registry.soy.registrar.payment}
/** Page allowing registrar to send registry money. */
{template .form}
{@param currencies: list<string>} /** Currencies in which customer can remit payment. */
{@param brainframe: uri} /** Location of Braintree iframe sandbox iframe HTML. */
<div class="{css('reg-payment')}">
<h1>Make a Payment</h1>
<p>
Please use the form below to pay your monthly invoice by credit card.
<p>
The bill you received from the registry should list an outstanding balance
for each currency. If you hold an outstanding balance in multiple currencies,
this form should be filled out and submitted separately for each one.
<form method="post" action="#" class="{css('reg-payment-form')}">
<fieldset>
<ul>
<li>
<label for="amount">Amount</label>
<input type="text"
id="amount"
name="amount"
autocomplete="off"
autofocus>
<li>
<label>Currency</label>
{call registry.soy.forms.menuButton}
{param id: 'currency' /}
{param selected: $currencies[0] /}
{param items: $currencies /}
{/call}
<li>
<label>Payment Method</label>
<iframe src="{$brainframe |blessStringAsTrustedResourceUrlForLegacy}"
id="method"
class="{css('reg-payment-form-method')}"
height="0"
width="100%"
frameBorder="0"
scrolling="no"></iframe>
<div class="{css('reg-payment-form-method-info')} {css('hidden')}"></div>
</ul>
<input type="submit" value="Submit Payment"
class="{css('reg-payment-form-submit')}{sp}
{css('kd-button')}{sp}
{css('kd-button-submit')}{sp}
{css('disabled')}">
<img alt="[Processing...]"
class="{css('reg-payment-form-loader')}"
src="/assets/images/loader1x.gif"
width="22" height="22">
</fieldset>
</form>
</div>
{/template}
/** Page allowing registrar to send registry money. */
{template .success}
{@param id: string} /** Transaction ID from payment gateway. */
{@param formattedAmount: string} /** Amount in which payment was made. */
<div class="{css('reg-payment')}">
<h1>Payment Processed</h1>
<p>
Your payment of {$formattedAmount} was successfully processed with
the Transaction ID {$id}.
<p>
<button class="{css('reg-payment-again')} {css('kd-button')} {css('kd-button-submit')}">
Make Another Payment
</button>
</div>
{/template}
/** Information about credit card payment method, once it's been entered. */
{template .methodInfoCard}
{@param cardType: string} /** Type of credit card, e.g. Visa. */
{@param lastTwo: string} /** Last two digits of credit card number. */
{if $cardType == 'Amex'}
American Express: xxxx xxxxxx xxx{$lastTwo}
{else}
{$cardType}: xxxx xxxx xxxx xx{$lastTwo}
{/if}
{/template}
/** Information about PayPal payment method, once it's been entered. */
{template .methodInfoPaypal}
{@param email: string} /** Email address associated with PayPal account. */
PayPal: {$email}
{/template}
/** Page used to block browsers without necessary features. */
{template .unsupported}
<div class="{css('reg-payment')}">
<img alt="[Crying Android]"
class="{css('reg-cryingAndroid')}"
src="/assets/images/android_sad.png"
width="183"
height="275">
<h1>Browser Unsupported</h1>
<p>
The Payment page requires features which are not present in your
browser. Please use one of the following compatible browsers:
<ul class="{css('reg-bullets')}">
<li>Chrome
<li>Android
<li>Safari
<li>Firefox
<li>IE 10+ or Edge
</ul>
</div>
{/template}
/** Page indicating customer is not on credit card billing terms. */
{template .notUsingCcBilling}
<div class="{css('reg-payment')}">
<h1>Payment Page Disabled</h1>
<p>
Your customer account is not on credit card billing terms. Please{sp}
<a href="/registrar#contact-us">contact support</a> to have your account
switched over.
</div>
{/template}

View file

@ -174,15 +174,6 @@ public class KmsKeyringTest {
assertThat(jsonCredential).isEqualTo("json-credential-stringmoo");
}
@Test
public void test_getBraintreePrivateKey() {
saveCleartextSecret("braintree-private-key-string");
String braintreePrivateKey = keyring.getBraintreePrivateKey();
assertThat(braintreePrivateKey).isEqualTo("braintree-private-key-stringmoo");
}
private static void persistSecret(String secretName, byte[] secretValue) {
KmsConnection kmsConnection = new FakeKmsConnection();

View file

@ -50,14 +50,14 @@ public class KmsUpdaterTest {
@Test
public void test_setMultipleSecrets() {
updater
.setBraintreePrivateKey("value1")
.setMarksdbDnlLogin("value1")
.setIcannReportingPassword("value2")
.setJsonCredential("value3")
.update();
verifySecretAndSecretRevisionWritten(
"braintree-private-key-string",
"braintree-private-key-string/foo",
"marksdb-dnl-login-string",
"marksdb-dnl-login-string/foo",
getCiphertext("value1"));
verifySecretAndSecretRevisionWritten(
"icann-reporting-password-string",
@ -67,16 +67,6 @@ public class KmsUpdaterTest {
"json-credential-string", "json-credential-string/foo", getCiphertext("value3"));
}
@Test
public void test_setBraintreePrivateKey() {
updater.setBraintreePrivateKey("value1").update();
verifySecretAndSecretRevisionWritten(
"braintree-private-key-string",
"braintree-private-key-string/foo",
getCiphertext("value1"));
}
@Test
public void test_setBrdaReceiverKey() throws Exception {
updater.setBrdaReceiverPublicKey(KmsTestHelper.getPublicKey()).update();

View file

@ -560,7 +560,6 @@ class google.registry.model.registrar.Registrar {
boolean premiumPriceAckRequired;
google.registry.model.CreateAutoTimestamp creationTime;
google.registry.model.UpdateAutoTimestamp lastUpdateTime;
google.registry.model.registrar.Registrar$BillingMethod billingMethod;
google.registry.model.registrar.Registrar$State state;
google.registry.model.registrar.Registrar$Type type;
google.registry.model.registrar.RegistrarAddress internationalizedAddress;
@ -591,10 +590,6 @@ class google.registry.model.registrar.Registrar$BillingAccountEntry {
java.lang.String accountId;
org.joda.money.CurrencyUnit currency;
}
enum google.registry.model.registrar.Registrar$BillingMethod {
BRAINTREE;
EXTERNAL;
}
enum google.registry.model.registrar.Registrar$State {
ACTIVE;
PENDING;

View file

@ -1,7 +1,5 @@
PATH CLASS METHODS OK AUTH_METHODS MIN USER_POLICY
/_dr/epp EppTlsAction POST n INTERNAL,API APP PUBLIC
/registrar ConsoleUiAction GET n INTERNAL,API,LEGACY NONE PUBLIC
/registrar-payment RegistrarPaymentAction POST n API,LEGACY USER PUBLIC
/registrar-payment-setup RegistrarPaymentSetupAction POST n API,LEGACY USER PUBLIC
/registrar-settings RegistrarSettingsAction POST n API,LEGACY USER PUBLIC
/registrar-xhr EppConsoleAction POST n API,LEGACY USER PUBLIC
PATH CLASS METHODS OK AUTH_METHODS MIN USER_POLICY
/_dr/epp EppTlsAction POST n INTERNAL,API APP PUBLIC
/registrar ConsoleUiAction GET n INTERNAL,API,LEGACY NONE PUBLIC
/registrar-settings RegistrarSettingsAction POST n API,LEGACY USER PUBLIC
/registrar-xhr EppConsoleAction POST n API,LEGACY USER PUBLIC

View file

@ -32,7 +32,6 @@ import google.registry.model.contact.ContactResource;
import google.registry.model.contact.PostalInfo;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.ofy.Ofy;
import google.registry.model.registrar.Registrar;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectRule;
import org.joda.time.DateTime;
@ -139,7 +138,6 @@ public enum Fixture {
loadRegistrar("TheRegistrar")
.asBuilder()
.setAllowedTlds(ImmutableSet.of("example", "xn--q9jyb4c"))
.setBillingMethod(Registrar.BillingMethod.BRAINTREE)
.build());
}
};

View file

@ -87,10 +87,6 @@ public final class RegistryTestServer {
// Registrar Console
route("/registrar", google.registry.module.frontend.FrontendServlet.class),
route("/registrar-settings",
google.registry.module.frontend.FrontendServlet.class),
route("/registrar-payment",
google.registry.module.frontend.FrontendServlet.class),
route("/registrar-payment-setup",
google.registry.module.frontend.FrontendServlet.class));
private static final ImmutableList<Class<? extends Filter>> FILTERS = ImmutableList.of(

View file

@ -54,7 +54,6 @@ public final class FakeKeyringModule {
private static final String MARKSDB_DNL_LOGIN = "dnl:yolo";
private static final String MARKSDB_LORDN_PASSWORD = "yolo";
private static final String MARKSDB_SMDRL_LOGIN = "smdrl:yolo";
private static final String BRAINTREE_PRIVATE_KEY = "braintree123";
private static final String JSON_CREDENTIAL = "json123";
@Provides
@ -145,11 +144,6 @@ public final class FakeKeyringModule {
return rdeReceiverKey;
}
@Override
public String getBraintreePrivateKey() {
return BRAINTREE_PRIVATE_KEY;
}
@Override
public void close() {}
};

View file

@ -21,6 +21,7 @@ import static google.registry.testing.TestDataHelper.filePath;
import static google.registry.testing.TestDataHelper.loadFile;
import com.google.common.base.Joiner;
import com.google.common.flogger.FluentLogger;
import google.registry.request.RouterDisplayHelper;
/**
@ -29,6 +30,8 @@ import google.registry.request.RouterDisplayHelper;
*/
public class GoldenFileTestHelper {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
String actualValue = null;
String nomulusCommand = null;
String goldenFileDescription = null;
@ -73,6 +76,7 @@ public class GoldenFileTestHelper {
checkNotNull(context);
checkNotNull(filename);
if (!actualValue.equals(loadFile(context, filename).trim())) {
logger.atWarning().log("Actual routing map was:\n\n%s", actualValue);
assert_()
.fail(
String.format(

View file

@ -29,7 +29,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.BillingMethod;
import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.Registrar.Type;
import google.registry.util.CidrAddressBlock;
@ -258,15 +257,6 @@ public class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarC
.containsExactly(CurrencyUnit.JPY, "123xyz", CurrencyUnit.USD, "abc123");
}
@Test
public void testSuccess_changeBillingMethodToBraintreeWhenBalanceIsZero() throws Exception {
createTlds("xn--q9jyb4c");
assertThat(loadRegistrar("NewRegistrar").getBillingMethod()).isEqualTo(BillingMethod.EXTERNAL);
runCommand("--billing_method=braintree", "--force", "NewRegistrar");
assertThat(loadRegistrar("NewRegistrar").getBillingMethod())
.isEqualTo(BillingMethod.BRAINTREE);
}
@Test
public void testSuccess_streetAddress() throws Exception {
runCommand("--street=\"1234 Main St\"", "--street \"4th Floor\"", "--street \"Suite 1\"",

View file

@ -1,97 +0,0 @@
// Copyright 2017 The Nomulus 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.
goog.setTestOnly();
goog.require('goog.dispose');
goog.require('goog.dom');
goog.require('goog.style');
goog.require('goog.testing.MockControl');
goog.require('goog.testing.asserts');
goog.require('goog.testing.jsunit');
goog.require('registry.registrar.BrainFrame');
goog.require('registry.testing');
var mocks = new goog.testing.MockControl();
var brainframe;
var mockPostMessage;
function setUp() {
registry.testing.addToDocument('<form><div id="bf"></div></form>');
brainframe = new registry.registrar.BrainFrame('omg', 'bf');
mockPostMessage =
mocks.createMethodMock(registry.registrar.BrainFrame, 'postMessage_');
}
function tearDown() {
goog.dispose(brainframe);
mocks.$tearDown();
}
function testRun_sendsTokenRequestToParent() {
mockPostMessage('{"type":"token_request"}', 'omg');
mocks.$replayAll();
brainframe.run();
mocks.$verifyAll();
}
function testTokenResponseMessage_callsSetup() {
var called = false;
goog.global.braintree = {};
goog.global.braintree.setup = function(token, mode, args) {
called = true;
assertEquals('imatoken', token);
assertEquals('dropin', mode);
assertEquals('bf', args.container.id);
};
brainframe.onMessage_({
getBrowserEvent: function() {
return {
source: window.parent,
origin: 'omg',
data: '{"type": "token_response", "token": "imatoken"}'
};
}
});
assertTrue(called);
}
function testPaymentMethodMessage_sendsInfo() {
mockPostMessage('{"type":"payment_method","submit":false,"method":"hi"}',
'omg');
mocks.$replayAll();
brainframe.onPaymentMethod_('hi');
mocks.$verifyAll();
}
function testOnResizeTimer_sendsHeight() {
mockPostMessage('{"type":"resize_request","height":123}', 'omg');
mocks.$replayAll();
goog.style.setHeight(goog.dom.getElement('bf'), 123);
brainframe.onResizeTimer_();
mocks.$verifyAll();
// But does not send height if size hasn't changed.
mocks.$replayAll();
brainframe.onResizeTimer_();
mocks.$verifyAll();
}

View file

@ -51,7 +51,6 @@ function setUp() {
logoutUrl: 'omg',
isAdmin: true,
clientId: test.testClientId,
showPaymentLink: false,
logoFilename: 'logo.png',
productName: 'Nomulus',
integrationEmail: 'integration@example.com',

View file

@ -50,7 +50,6 @@ function setUp() {
logoutUrl: 'omg',
isAdmin: true,
clientId: test.testClientId,
showPaymentLink: false,
logoFilename: 'logo.png',
productName: 'Nomulus',
integrationEmail: 'integration@google.com',

View file

@ -41,7 +41,6 @@ function setUp() {
logoutUrl: 'omg',
isAdmin: true,
clientId: 'daddy',
showPaymentLink: false,
logoFilename: 'logo.png',
productName: 'Nomulus',
integrationEmail: 'integration@example.com',

View file

@ -47,7 +47,6 @@ function setUp() {
logoutUrl: 'https://justinetunney.com',
isAdmin: true,
clientId: 'ignore',
showPaymentLink: false,
logoFilename: 'logo.png',
productName: 'Nomulus',
integrationEmail: 'integration@example.com',

View file

@ -47,7 +47,6 @@ function setUp() {
logoutUrl: 'https://example.com',
isAdmin: true,
clientId: 'ignore',
showPaymentLink: false,
logoFilename: 'logo.png',
productName: 'Nomulus',
integrationEmail: 'integration@example.com',

View file

@ -1,208 +0,0 @@
// Copyright 2017 The Nomulus 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.
goog.setTestOnly();
goog.require('goog.dispose');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.events.EventType');
goog.require('goog.json');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.asserts');
goog.require('goog.testing.events');
goog.require('goog.testing.events.Event');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.net.XhrIo');
goog.require('registry.registrar.Payment');
goog.require('registry.testing');
var $ = goog.dom.getRequiredElement;
var $$ = goog.dom.getRequiredElementByClass;
var stubs = new goog.testing.PropertyReplacer();
var page;
function setUp() {
registry.testing.addToDocument('<div id="reg-content"></div>');
registry.testing.addToDocument('<div class="kd-butterbar"></div>');
registry.testing.addToDocument('<div class="kd-butterbar-text"></div>');
stubs.setPath('goog.net.XhrIo', goog.testing.net.XhrIo);
page = new registry.registrar.Payment(null, '?');
page.bindToDom();
}
function tearDown() {
goog.dispose(page);
stubs.reset();
goog.testing.net.XhrIo.cleanup();
}
function testRenderForm() {
registry.testing.assertReqMockRsp(
'?',
'/registrar-payment-setup',
{},
{
status: 'SUCCESS',
results: [
{
token: 'omg-im-a-token',
currencies: ['LOL', 'OMG'],
brainframe: ''
}
]
});
assertEquals('', $('amount').value);
assertEquals('LOL', goog.dom.getTextContent($$('selected', $('currency'))));
assertTrue(
goog.dom.classlist.contains($$('reg-payment-form-submit'), 'disabled'));
}
function testResize() {
testRenderForm();
send({
type: 'resize_request',
height: 123
});
assertEquals('123', $$('reg-payment-form-method').height);
}
function testReady() {
testRenderForm();
send({type: 'ready'});
assertFalse(
goog.dom.classlist.contains($$('reg-payment-form-submit'), 'disabled'));
}
function testPaymentMethodCard() {
testRenderForm();
send({
type: 'payment_method',
method: {
type: 'CreditCard',
nonce: 'omg-im-a-nonce',
details: {
cardType: 'Amex',
lastTwo: '12'
}
}
});
assertEquals(
'American Express: xxxx xxxxxx xxx12',
goog.dom.getTextContent($$('reg-payment-form-method-info')));
}
function testPaymentMethodPaypal() {
testRenderForm();
send({
type: 'payment_method',
method: {
type: 'PayPalAccount',
nonce: 'omg-im-a-nonce',
details: {
email: 'sparrows@nightingales.example'
}
}
});
assertEquals(
'PayPal: sparrows@nightingales.example',
goog.dom.getTextContent($$('reg-payment-form-method-info')));
}
function testBadAmount_displaysError() {
testPaymentMethodCard();
$('amount').value = '3.14';
submit();
registry.testing.assertReqMockRsp(
'?',
'/registrar-payment',
{
amount: '3.14',
currency: 'LOL',
paymentMethodNonce: 'omg-im-a-nonce'
},
{
status: 'ERROR',
message: 'gimmeh moar money',
field: 'amount'
});
assertTrue(goog.dom.classlist.contains($('amount'), 'kd-formerror'));
assertEquals('gimmeh moar money',
goog.dom.getTextContent($$('kd-errormessage')));
}
function testGoodPayment_displaysSuccessPage() {
testPaymentMethodCard();
$('amount').value = '314';
submit();
registry.testing.assertReqMockRsp(
'?',
'/registrar-payment',
{
amount: '314',
currency: 'LOL',
paymentMethodNonce: 'omg-im-a-nonce'
},
{
status: 'SUCCESS',
results: [
{
id: 'omg-im-an-id',
formattedAmount: '$314'
}
]
});
assertContains('Payment Processed',
goog.dom.getTextContent($$('reg-payment')));
assertContains('omg-im-an-id',
goog.dom.getTextContent($$('reg-payment')));
assertContains('$314',
goog.dom.getTextContent($$('reg-payment')));
}
/**
* Sends message to page.
* @param {string} message
*/
function send(message) {
page.onMessage_({
getBrowserEvent: function() {
return {
source: goog.dom.getFrameContentWindow($$('reg-payment-form-method')),
data: goog.json.serialize(message)
};
}
});
}
/** Submits payment form. */
function submit() {
goog.testing.events.fireBrowserEvent(
new goog.testing.events.Event(
goog.events.EventType.SUBMIT,
$$('reg-payment-form')));
}

View file

@ -55,7 +55,6 @@ function setUp() {
isAdmin: true,
xsrfToken: test.testXsrfToken,
clientId: test.testClientId,
showPaymentLink: false,
logoFilename: 'logo.png',
productName: 'Nomulus',
integrationEmail: 'integration@example.com',

View file

@ -49,7 +49,6 @@ function setUp() {
logoutUrl: 'omg',
isAdmin: true,
clientId: test.testClientId,
showPaymentLink: false,
logoFilename: 'logo.png',
productName: 'Nomulus',
integrationEmail: 'integration@example.com',

View file

@ -12,7 +12,6 @@ java_library(
srcs = glob(["*.java"]),
resources = glob(["testdata/*"]),
deps = [
"//java/google/registry/braintree",
"//java/google/registry/config",
"//java/google/registry/export/sheet",
"//java/google/registry/model",

View file

@ -1,445 +0,0 @@
// Copyright 2017 The Nomulus 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 google.registry.ui.server.registrar;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatastoreHelper.loadRegistrar;
import static google.registry.testing.ReflectiveFieldExtractor.extractField;
import static java.util.Arrays.asList;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.braintreegateway.BraintreeGateway;
import com.braintreegateway.Result;
import com.braintreegateway.Transaction;
import com.braintreegateway.Transaction.GatewayRejectionReason;
import com.braintreegateway.TransactionGateway;
import com.braintreegateway.TransactionRequest;
import com.braintreegateway.ValidationError;
import com.braintreegateway.ValidationErrorCode;
import com.braintreegateway.ValidationErrors;
import com.google.appengine.api.users.User;
import com.google.common.collect.ImmutableMap;
import google.registry.request.auth.AuthLevel;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.AppEngineRule;
import google.registry.testing.MockitoJUnitRule;
import java.math.BigDecimal;
import javax.servlet.http.HttpServletRequest;
import org.joda.money.CurrencyUnit;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
/** Tests for {@link RegistrarPaymentAction}. */
@RunWith(JUnit4.class)
public class RegistrarPaymentActionTest {
@Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build();
@Rule public final MockitoJUnitRule mocks = MockitoJUnitRule.create();
@Mock private BraintreeGateway braintreeGateway;
@Mock private TransactionGateway transactionGateway;
@Mock private Result<Transaction> result;
@Mock private Transaction transaction;
@Mock private ValidationErrors validationErrors;
@Captor private ArgumentCaptor<TransactionRequest> transactionRequestCaptor;
private final SessionUtils sessionUtils = mock(SessionUtils.class);
private final User user = new User("marla.singer@example.com", "gmail.com", "12345");
private final RegistrarPaymentAction paymentAction = new RegistrarPaymentAction();
@Before
public void before() {
paymentAction.sessionUtils = sessionUtils;
paymentAction.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, false));
paymentAction.accountIds =
ImmutableMap.of(
CurrencyUnit.USD, "merchant-account-usd",
CurrencyUnit.JPY, "merchant-account-jpy");
paymentAction.braintreeGateway = braintreeGateway;
when(braintreeGateway.transaction()).thenReturn(transactionGateway);
when(transactionGateway.sale(any(TransactionRequest.class))).thenReturn(result);
when(sessionUtils.getRegistrarForAuthResult(
any(HttpServletRequest.class), any(AuthResult.class)))
.thenReturn(loadRegistrar("TheRegistrar"));
}
@Test
public void testCurrencyIsUsd_usesAmericanMerchantAccount() throws Exception {
when(result.isSuccess()).thenReturn(true);
when(result.getTarget()).thenReturn(transaction);
when(transaction.getId()).thenReturn("omg-im-an-id");
when(transaction.getAmount()).thenReturn(BigDecimal.valueOf(123.4));
when(transaction.getCurrencyIsoCode()).thenReturn("USD");
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", " 123.4 ",
"currency", "USD",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "SUCCESS",
"message", "Payment processed successfully",
"results", asList(
ImmutableMap.of(
"id", "omg-im-an-id",
"formattedAmount", "$123.40")));
verify(transactionGateway).sale(transactionRequestCaptor.capture());
TransactionRequest transactionRequest = transactionRequestCaptor.getAllValues().get(0);
assertThat(extractField(BigDecimal.class, transactionRequest, "amount"))
.isEqualTo(BigDecimal.valueOf(123.4).setScale(2));
assertThat(extractField(String.class, transactionRequest, "merchantAccountId"))
.isEqualTo("merchant-account-usd");
assertThat(extractField(String.class, transactionRequest, "customerId"))
.isEqualTo("TheRegistrar");
}
@Test
public void testCurrencyIsJpy_usesJapaneseMerchantAccount() throws Exception {
when(result.isSuccess()).thenReturn(true);
when(result.getTarget()).thenReturn(transaction);
when(transaction.getId()).thenReturn("omg-im-an-id");
when(transaction.getAmount()).thenReturn(BigDecimal.valueOf(123));
when(transaction.getCurrencyIsoCode()).thenReturn("JPY");
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "123",
"currency", "JPY",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "SUCCESS",
"message", "Payment processed successfully",
"results", asList(
ImmutableMap.of(
"id", "omg-im-an-id",
"formattedAmount", "JPY 123")));
verify(transactionGateway).sale(transactionRequestCaptor.capture());
TransactionRequest transactionRequest = transactionRequestCaptor.getAllValues().get(0);
assertThat(extractField(BigDecimal.class, transactionRequest, "amount"))
.isEqualTo(BigDecimal.valueOf(123));
assertThat(extractField(String.class, transactionRequest, "merchantAccountId"))
.isEqualTo("merchant-account-jpy");
}
@Test
public void testAmountNotPresent_returnsErrorOnAmountField() {
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"currency", "JPY",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "This field is required.",
"field", "amount",
"results", asList());
}
@Test
public void testAmountNotNumber_returnsErrorOnAmountField() {
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "abc",
"currency", "JPY",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Invalid number.",
"field", "amount",
"results", asList());
}
@Test
public void testNegativeAmount_returnsErrorOnAmountField() {
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "-10",
"currency", "JPY",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Must be a positive number.",
"field", "amount",
"results", asList());
}
@Test
public void testZeroAmount_returnsErrorOnAmountField() {
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "0",
"currency", "JPY",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Must be a positive number.",
"field", "amount",
"results", asList());
}
@Test
public void testAmountHasMorePrecisionThanUsdCurrencyAllows_returnsError() {
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "123.456",
"currency", "USD",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Scale of amount 123.456 is greater than the scale of the currency USD",
"field", "amount",
"results", asList());
}
@Test
public void testAmountHasMorePrecisionThanJpyCurrencyAllows_returnsError() {
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "1.1",
"currency", "JPY",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Scale of amount 1.1 is greater than the scale of the currency JPY",
"field", "amount",
"results", asList());
}
@Test
public void testCurrencyValidButNotSupported_returnsErrorOnCurrencyField() {
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "123",
"currency", "EUR",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Unsupported currency.",
"field", "currency",
"results", asList());
}
@Test
public void testCurrencyBogus_returnsErrorOnCurrencyField() {
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "123",
"currency", "rm -rf /etc/passwd",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Invalid currency code.",
"field", "currency",
"results", asList());
}
@Test
public void testCurrencyCodeNotAssigned_returnsErrorOnCurrencyField() {
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "123",
"currency", "ZZZ",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Unknown ISO currency code.",
"field", "currency",
"results", asList());
}
@Test
public void testCurrencyMissing_returnsErrorOnCurrencyField() {
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "123",
"paymentMethodNonce", "omg")))
.containsExactly(
"status", "ERROR",
"message", "This field is required.",
"field", "currency",
"results", asList());
}
@Test
public void testPaymentMethodMissing_returnsErrorOnPaymentMethodField() {
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "123",
"currency", "JPY")))
.containsExactly(
"status", "ERROR",
"message", "This field is required.",
"field", "paymentMethodNonce",
"results", asList());
}
@Test
public void testProcessorDeclined_returnsErrorResponse() {
when(result.isSuccess()).thenReturn(false);
when(result.getTransaction()).thenReturn(transaction);
when(transaction.getStatus()).thenReturn(Transaction.Status.PROCESSOR_DECLINED);
when(transaction.getProcessorResponseText())
.thenReturn("You don't know the power of the dark side");
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "123",
"currency", "USD",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Payment declined: You don't know the power of the dark side",
"results", asList());
}
@Test
public void testGatewayRejectedDueToCvv_returnsErrorResponse() {
when(result.isSuccess()).thenReturn(false);
when(result.getTransaction()).thenReturn(transaction);
when(transaction.getStatus()).thenReturn(Transaction.Status.GATEWAY_REJECTED);
when(transaction.getGatewayRejectionReason()).thenReturn(GatewayRejectionReason.CVV);
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "123",
"currency", "USD",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Payment rejected: Invalid CVV code.",
"results", asList());
}
@Test
public void testGatewayRejectedDueToAddress_returnsErrorResponse() {
when(result.isSuccess()).thenReturn(false);
when(result.getTransaction()).thenReturn(transaction);
when(transaction.getStatus()).thenReturn(Transaction.Status.GATEWAY_REJECTED);
when(transaction.getGatewayRejectionReason()).thenReturn(GatewayRejectionReason.AVS);
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "123",
"currency", "USD",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Payment rejected: Invalid address.",
"results", asList());
}
@Test
public void testSettlementDeclined_returnsErrorResponse() {
when(result.isSuccess()).thenReturn(false);
when(result.getTransaction()).thenReturn(transaction);
when(transaction.getStatus()).thenReturn(Transaction.Status.SETTLEMENT_DECLINED);
when(transaction.getProcessorSettlementResponseText())
.thenReturn("mine eyes have seen the glory of the coming of the borg");
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "123",
"currency", "USD",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Payment declined: mine eyes have seen the glory of the coming of the borg",
"results", asList());
}
@Test
public void testHasTransactionObjectWithWeirdStatus_returnsErrorResponse() {
when(result.isSuccess()).thenReturn(false);
when(result.getTransaction()).thenReturn(transaction);
when(transaction.getStatus()).thenReturn(Transaction.Status.UNRECOGNIZED);
when(transaction.getProcessorResponseText())
.thenReturn("he is preempting the instance where the deadlocked shards are stored");
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "123",
"currency", "USD",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Payment failure: "
+ "he is preempting the instance where the deadlocked shards are stored",
"results", asList());
}
@Test
public void testFailedWithWeirdStatusButNoHelpfulText_usesWeirdStatusAsMsg() {
when(result.isSuccess()).thenReturn(false);
when(result.getTransaction()).thenReturn(transaction);
when(transaction.getStatus()).thenReturn(Transaction.Status.FAILED);
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "123",
"currency", "USD",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Payment failure: FAILED",
"results", asList());
}
@Test
public void testRemoteValidationError_showsErrorOnlyOnFirstFormField() {
when(result.isSuccess()).thenReturn(false);
when(result.getErrors()).thenReturn(validationErrors);
when(validationErrors.getAllDeepValidationErrors())
.thenReturn(asList(
new ValidationError(
"amount",
ValidationErrorCode.TRANSACTION_SETTLEMENT_AMOUNT_IS_LESS_THAN_SERVICE_FEE_AMOUNT,
"Gimmeh moar moneys"),
new ValidationError(
"fax",
ValidationErrorCode.CUSTOMER_FAX_IS_TOO_LONG,
"Fax is too long")));
assertThat(
paymentAction.handleJsonRequest(
ImmutableMap.of(
"amount", "123",
"currency", "USD",
"paymentMethodNonce", "abc-123")))
.containsExactly(
"status", "ERROR",
"message", "Gimmeh moar moneys",
"field", "amount",
"results", asList());
}
}

View file

@ -1,122 +0,0 @@
// Copyright 2017 The Nomulus 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 google.registry.ui.server.registrar;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatastoreHelper.loadRegistrar;
import static google.registry.testing.DatastoreHelper.persistResource;
import static java.util.Arrays.asList;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.braintreegateway.BraintreeGateway;
import com.braintreegateway.ClientTokenGateway;
import com.google.appengine.api.users.User;
import com.google.common.collect.ImmutableMap;
import google.registry.braintree.BraintreeRegistrarSyncer;
import google.registry.model.registrar.Registrar;
import google.registry.request.auth.AuthLevel;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.AppEngineRule;
import javax.servlet.http.HttpServletRequest;
import org.joda.money.CurrencyUnit;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link RegistrarPaymentSetupAction}. */
@RunWith(JUnit4.class)
public class RegistrarPaymentSetupActionTest {
@Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build();
private final BraintreeGateway braintreeGateway = mock(BraintreeGateway.class);
private final ClientTokenGateway clientTokenGateway = mock(ClientTokenGateway.class);
private final BraintreeRegistrarSyncer customerSyncer = mock(BraintreeRegistrarSyncer.class);
private final SessionUtils sessionUtils = mock(SessionUtils.class);
private final User user = new User("marla.singer@example.com", "gmail.com", "12345");
private final RegistrarPaymentSetupAction action = new RegistrarPaymentSetupAction();
@Before
public void before() {
action.sessionUtils = sessionUtils;
action.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, false));
action.braintreeGateway = braintreeGateway;
action.customerSyncer = customerSyncer;
Registrar registrar = persistResource(
loadRegistrar("TheRegistrar")
.asBuilder()
.setBillingMethod(Registrar.BillingMethod.BRAINTREE)
.build());
when(sessionUtils.getRegistrarForAuthResult(
any(HttpServletRequest.class), any(AuthResult.class)))
.thenReturn(registrar);
when(braintreeGateway.clientToken()).thenReturn(clientTokenGateway);
}
@Test
public void testTokenGeneration() {
action.brainframe = "/doodle";
action.accountIds =
ImmutableMap.of(
CurrencyUnit.USD, "sorrow",
CurrencyUnit.JPY, "torment");
String blanketsOfSadness = "our hearts are beating, but no one is breathing";
when(clientTokenGateway.generate()).thenReturn(blanketsOfSadness);
assertThat(action.handleJsonRequest(ImmutableMap.of()))
.containsExactly(
"status", "SUCCESS",
"message", "Success",
"results", asList(
ImmutableMap.of(
"token", blanketsOfSadness,
"currencies", asList("USD", "JPY"),
"brainframe", "/doodle")));
verify(customerSyncer).sync(eq(loadRegistrar("TheRegistrar")));
}
@Test
public void testNonEmptyRequestObject_returnsError() {
assertThat(action.handleJsonRequest(ImmutableMap.of("oh", "no")))
.containsExactly(
"status", "ERROR",
"message", "JSON request object must be empty",
"results", asList());
}
@Test
public void testNotOnCreditCardBillingTerms_showsErrorPage() {
Registrar registrar = persistResource(
loadRegistrar("TheRegistrar")
.asBuilder()
.setBillingMethod(Registrar.BillingMethod.EXTERNAL)
.build());
when(sessionUtils.getRegistrarForAuthResult(
any(HttpServletRequest.class), any(AuthResult.class)))
.thenReturn(registrar);
assertThat(action.handleJsonRequest(ImmutableMap.of()))
.containsExactly(
"status", "ERROR",
"message", "not-using-cc-billing",
"results", asList());
}
}