Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ A full end-to-end integration demo is available [here](https://github.com/killbi
## Requirements

* An active Braintree account is required for using the plugin. A Braintree sandbox account may be used for testing purposes.
* The plugin needs a database. The latest version of the schema can be found [here](https://github.com/killbill/killbill-braintree/tree/master/src/main/resources).
* The plugin needs a database. The database tables are automatically created and updated at plugin startup by default. Alternatively, if you would like to manage the database schema manually, you can disable automatic migrations and use the SQL scripts provided in the (https://github.com/killbill/killbill-braintree/tree/master/src/main/resources/migration)[src/main/resources/migration] directory to create or update the database tables as needed. See the [Configuration](#configuration) section below for details about disabling automatic migrations.


## Build

Expand Down Expand Up @@ -73,6 +74,23 @@ Some important notes:
* The plugin attempts to load the credentials either from the per-tenant configuration or the Kill Bill properties file while the unit tests require the properties to be set as environment variables.
* In order to facilitate automated testing, you should disable all fraud detection within your sandbox account. These can generate gateway rejection errors when processing multiple test transactions. In particular make sure to disable [Duplicate Transaction Checking](https://articles.braintreepayments.com/control-panel/transactions/duplicate-checking#configuring-duplicate-transaction-checking).

In addition, the `org.killbill.billing.plugin.braintree.runMigrations` property can be used to control Flyway DB migrations at plugin startup. This property eliminates the need to manually install or update the database tables, as the plugin will handle schema setup automatically.

Default value:

```properties
org.killbill.billing.plugin.braintree.runMigrations=true
```

To skip automatic migrations (for example, if you prefer to manage the database schema manually), set:

```properties
org.killbill.billing.plugin.braintree.runMigrations=false
```


When `org.killbill.billing.plugin.braintree.runMigrations` is disabled, ensure that the required database tables are created manually using the SQL scripts provided in the (https://github.com/killbill/killbill-braintree/tree/master/src/main/resources/migration)[src/main/resources/migration] directory.

## Testing

1. Ensure that the plugin is installed and configured as explained above.
Expand Down Expand Up @@ -223,6 +241,3 @@ curl -v \
-H "X-Killbill-Comment: demo" \
"http://127.0.0.1:8080/1.0/kb/accounts/<ACCOUNT_ID>/paymentMethods/refresh"
```



5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@
<artifactId>org.apache.felix.framework</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>7.7.3</version>
</dependency>
<dependency>
<groupId>org.jooby</groupId>
<artifactId>jooby</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@

package org.killbill.billing.plugin.braintree.core;

import java.sql.SQLException;
import java.util.Hashtable;

import javax.servlet.Servlet;
import javax.servlet.http.HttpServlet;

import org.flywaydb.core.Flyway;
import org.killbill.billing.osgi.api.Healthcheck;
import org.killbill.billing.osgi.api.OSGIPluginProperties;
import org.killbill.billing.osgi.libs.killbill.KillbillActivatorBase;
Expand All @@ -33,6 +35,8 @@
import org.killbill.billing.plugin.core.config.PluginEnvironmentConfig;
import org.killbill.billing.plugin.core.resources.jooby.PluginApp;
import org.killbill.billing.plugin.core.resources.jooby.PluginAppBuilder;
import org.killbill.billing.plugin.dao.PluginDao;
import org.killbill.billing.plugin.dao.PluginDao.DBEngine;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -49,6 +53,7 @@ public void start(final BundleContext context) throws Exception {
super.start(context);
final String region = PluginEnvironmentConfig.getRegion(configProperties.getProperties());

runMigrationsIfEnabled();

// Register an event listener for plugin configuration
braintreeConfigurationHandler = new BraintreeConfigPropertiesConfigurationHandler(region, PLUGIN_NAME, killbillAPI);
Expand Down Expand Up @@ -108,4 +113,41 @@ private void registerHealthcheck(final BundleContext context, final Healthcheck
props.put(OSGIPluginProperties.PLUGIN_NAME_PROP, PLUGIN_NAME);
registrar.registerService(context, Healthcheck.class, healthcheck, props);
}

private void runMigrationsIfEnabled() {
// Run Flyway migrations to create/update database tables
if (BraintreeConfigProperties.shouldRunMigrations(configProperties.getProperties())) {
DBEngine dbEngine;
try {
dbEngine = PluginDao.getDBEngine(dataSource.getDataSource());
} catch (final SQLException e) {
logger.warn("Unable to determine database engine, defaulting to MySQL migrations", e);
dbEngine = DBEngine.MYSQL;
}

final String locations;
switch (dbEngine) {
case POSTGRESQL:
locations = "classpath:migration/postgresql";
break;
case GENERIC:
case H2:
case MYSQL:
default:
// H2 and GENERIC use MySQL-compatible migration scripts
locations = "classpath:migration/mysql";
break;
}

final Flyway flyway = Flyway.configure(getClass().getClassLoader())
.dataSource(dataSource.getDataSource())
.locations(locations)
.table("braintree_schema_history")
.baselineOnMigrate(true)
.load();
flyway.migrate();
} else {
logger.info("Skipping Flyway migrations as 'runMigrations' is set to false");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public class BraintreeConfigProperties {
private static final String KEY_VALUE_DELIMITER = "#";
private static final String DEFAULT_CONNECTION_TIMEOUT = "30000";
private static final String DEFAULT_READ_TIMEOUT = "60000";

private static final String DEFAULT_RUN_MIGRATIONS = "true";

private final String region;
private final String btEnvironment;
private final String btMerchantId;
Expand All @@ -54,7 +55,8 @@ public class BraintreeConfigProperties {
private final Map<String, Period> paymentMethodToExpirationPeriod = new LinkedHashMap<String, Period>();
private final String chargeDescription;
private final String chargeStatementDescriptor;

private final boolean runMigrations;

public BraintreeConfigProperties(final Properties properties, final String region) {
this.region = region;
this.btEnvironment = properties.getProperty(PROPERTY_PREFIX + "btEnvironment", "sandbox");
Expand All @@ -66,6 +68,7 @@ public BraintreeConfigProperties(final Properties properties, final String regio
this.pendingPaymentExpirationPeriod = readPendingExpirationProperty(properties);
this.chargeDescription = Ascii.truncate(MoreObjects.firstNonNull(properties.getProperty(PROPERTY_PREFIX + "chargeDescription"), "Kill Bill charge"), 22, "...");
this.chargeStatementDescriptor = Ascii.truncate(MoreObjects.firstNonNull(properties.getProperty(PROPERTY_PREFIX + "chargeStatementDescriptor"), "Kill Bill charge"), 22, "...");
this.runMigrations = Boolean.parseBoolean(properties.getProperty(PROPERTY_PREFIX + "runMigrations", DEFAULT_RUN_MIGRATIONS));
}

public String getRegion() {
Expand Down Expand Up @@ -116,6 +119,14 @@ public String getChargeStatementDescriptor() {
return chargeStatementDescriptor;
}

public boolean isRunMigrations() {
return runMigrations;
}

public static boolean shouldRunMigrations(final Properties properties) {
return Boolean.parseBoolean(properties.getProperty(PROPERTY_PREFIX + "runMigrations", DEFAULT_RUN_MIGRATIONS));
}

public Period getPendingPaymentExpirationPeriod(@Nullable final String paymentMethod) {
if (paymentMethod != null && paymentMethodToExpirationPeriod.get(paymentMethod.toLowerCase()) != null) {
return paymentMethodToExpirationPeriod.get(paymentMethod.toLowerCase());
Expand Down
10 changes: 8 additions & 2 deletions src/main/resources/ddl-postgresql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
*/

/* We cannot use timestamp in MySQL because of the implicit TimeZone conversions it does behind the scenes */
CREATE DOMAIN datetime AS timestamp without time zone;
DO $$ BEGIN
CREATE DOMAIN datetime AS timestamp without time zone;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;

CREATE DOMAIN longtext AS text;
DO $$ BEGIN
CREATE DOMAIN longtext AS text;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2021 Wovenware, Inc
*
* Wovenware licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

create table braintree_responses (
record_id serial
, kb_account_id char(36) not null
, kb_payment_id char(36) not null
, kb_payment_transaction_id char(36) not null
, transaction_type varchar(32) not null
, amount numeric(15,9)
, currency char(3)
, braintree_id varchar(255) not null
, additional_data longtext default null
, created_date datetime not null
, kb_tenant_id char(36) not null
, primary key(record_id)
) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
create index braintree_responses_kb_payment_id on braintree_responses(kb_payment_id);
create index braintree_responses_kb_payment_transaction_id on braintree_responses(kb_payment_transaction_id);
create index braintree_responses_braintree_id on braintree_responses(braintree_id);

create table braintree_payment_methods (
record_id serial
, kb_account_id char(36) not null
, kb_payment_method_id char(36) not null
, braintree_id varchar(255) not null
, is_deleted smallint not null default 0
, additional_data longtext default null
, created_date datetime not null
, updated_date datetime not null
, kb_tenant_id char(36) not null
, primary key(record_id)
) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
create unique index braintree_payment_methods_kb_payment_id on braintree_payment_methods(kb_payment_method_id);
create index braintree_payment_methods_braintree_id on braintree_payment_methods(braintree_id);
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2020-2020 Equinix, Inc
* Copyright 2014-2020 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

/* We cannot use timestamp in MySQL because of the implicit TimeZone conversions it does behind the scenes */
DO $$ BEGIN
CREATE DOMAIN datetime AS timestamp without time zone;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;

DO $$ BEGIN
CREATE DOMAIN longtext AS text;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;

create table braintree_responses (
record_id serial
, kb_account_id char(36) not null
, kb_payment_id char(36) not null
, kb_payment_transaction_id char(36) not null
, transaction_type varchar(32) not null
, amount numeric(15,9)
, currency char(3)
, braintree_id varchar(255) not null
, additional_data longtext default null
, created_date datetime not null
, kb_tenant_id char(36) not null
, primary key(record_id)
) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
create index braintree_responses_kb_payment_id on braintree_responses(kb_payment_id);
create index braintree_responses_kb_payment_transaction_id on braintree_responses(kb_payment_transaction_id);
create index braintree_responses_braintree_id on braintree_responses(braintree_id);

create table braintree_payment_methods (
record_id serial
, kb_account_id char(36) not null
, kb_payment_method_id char(36) not null
, braintree_id varchar(255) not null
, is_deleted smallint not null default 0
, additional_data longtext default null
, created_date datetime not null
, updated_date datetime not null
, kb_tenant_id char(36) not null
, primary key(record_id)
) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
create unique index braintree_payment_methods_kb_payment_id on braintree_payment_methods(kb_payment_method_id);
create index braintree_payment_methods_braintree_id on braintree_payment_methods(braintree_id);

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright 2020-2020 Equinix, Inc
* Copyright 2014-2020 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

ALTER TABLE braintree_payment_methods ADD COLUMN is_default SMALLINT NOT NULL DEFAULT 0;
Loading