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

@ -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}