diff --git a/src/bambora-payment-starter/README.md b/src/bambora-payment-starter/README.md
new file mode 100644
index 0000000..536a938
--- /dev/null
+++ b/src/bambora-payment-starter/README.md
@@ -0,0 +1,14 @@
+# Read Me First
+The following was discovered as part of building this project:
+
+* The original package name 'ca.bc.gov.open.bambora-payment-starter' is invalid and this project uses 'ca.bc.gov.open.bamborapaymentstarter' instead.
+
+# Getting Started
+
+### Reference Documentation
+For further reference, please consider the following sections:
+
+* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html)
+* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.3.3.RELEASE/maven-plugin/reference/html/)
+* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.3.3.RELEASE/maven-plugin/reference/html/#build-image)
+
diff --git a/src/bambora-payment-starter/pom.xml b/src/bambora-payment-starter/pom.xml
new file mode 100644
index 0000000..02316ac
--- /dev/null
+++ b/src/bambora-payment-starter/pom.xml
@@ -0,0 +1,84 @@
+
+
+ 4.0.0
+
+ ca.bc.gov.open
+ bambora-payment-starter
+ 0.1.5
+
+
+ 1.8
+ 2.2.4.RELEASE
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web-services
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+ commons-codec
+ commons-codec
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring-boot.version}
+ pom
+ import
+
+
+ ca.bc.gov.open
+ spring-starters-bom
+ 0.1.5
+ pom
+ import
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.5.1
+
+
+ ${java.version}
+
+
+
+
+
+
diff --git a/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/AutoConfiguration.java b/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/AutoConfiguration.java
new file mode 100644
index 0000000..0edb517
--- /dev/null
+++ b/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/AutoConfiguration.java
@@ -0,0 +1,25 @@
+package ca.bc.gov.open.bambora.payment.starter;
+
+import ca.bc.gov.open.bambora.payment.starter.managment.BamboraCardService;
+import ca.bc.gov.open.bambora.payment.starter.managment.BamboraCardServiceImpl;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableConfigurationProperties(BamboraProperties.class)
+public class AutoConfiguration {
+
+ private final BamboraProperties bamboraProperties;
+
+ public AutoConfiguration(BamboraProperties bamboraProperties) {
+ this.bamboraProperties = bamboraProperties;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(BamboraCardService.class)
+ public BamboraCardService bamboraCardService() {
+ return new BamboraCardServiceImpl(bamboraProperties);
+ }
+}
diff --git a/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/BamboraConstants.java b/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/BamboraConstants.java
new file mode 100644
index 0000000..c3031ed
--- /dev/null
+++ b/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/BamboraConstants.java
@@ -0,0 +1,23 @@
+package ca.bc.gov.open.bambora.payment.starter;
+
+public class BamboraConstants {
+ public static final String PARAM_PPRDIR_SERVICE_VERSION = "serviceVersion";
+ public static final String PARAM_PPRDIR_MERCHANT_ID = "merchantId";
+ public static final String PARAM_PPRDIR_LANGUAGE = "trnLanguage";
+ public static final String PARAM_PPRDIR_OPERATION_TYPE = "operationType";
+ public static final String PARAM_PPRDIR_RETURN_URL = "trnReturnURL";
+ public static final String PARAM_PPRDIR_ORDER_NUMBER = "trnOrderNumber";
+ public static final String PARAM_PPRDIR_CUSTOMER_CODE = "customerCode";
+ public static final String PARAM_PPRDIR_REF1 = "ref1";
+ public static final String LANGUAGE_TYPE = "eng";
+ public static final String PARAM_TRANS_HASH_VALUE = "hashValue";
+ public static final String PARAM_TRANS_HASH_EXPIRY = "hashExpiry";
+ public static final String PARAM_TRANS_HASH_EXPIRY_FORMAT = "yyyyMMddkkmm";
+
+
+ public enum OperationTypes {
+ N, M
+ }
+
+
+}
diff --git a/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/BamboraException.java b/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/BamboraException.java
new file mode 100644
index 0000000..3204e1d
--- /dev/null
+++ b/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/BamboraException.java
@@ -0,0 +1,9 @@
+package ca.bc.gov.open.bambora.payment.starter;
+
+public class BamboraException extends RuntimeException {
+
+ public BamboraException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/BamboraProperties.java b/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/BamboraProperties.java
new file mode 100644
index 0000000..94cc0ec
--- /dev/null
+++ b/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/BamboraProperties.java
@@ -0,0 +1,56 @@
+package ca.bc.gov.open.bambora.payment.starter;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableConfigurationProperties(BamboraProperties.class)
+@ConfigurationProperties(prefix = "bambora")
+public class BamboraProperties {
+ private String merchantId;
+ private String hostedProfileUrl;
+ private String hostedProfileServiceVersion;
+ private String hashKey;
+ private int minutesToExpiry;
+
+ public String getMerchantId() {
+ return merchantId;
+ }
+
+ public void setMerchantId(String merchantId) {
+ this.merchantId = merchantId;
+ }
+
+ public String getHostedProfileUrl() {
+ return hostedProfileUrl;
+ }
+
+ public void setHostedProfileUrl(String hostedProfileUrl) {
+ this.hostedProfileUrl = hostedProfileUrl;
+ }
+
+ public String getHostedProfileServiceVersion() {
+ return hostedProfileServiceVersion;
+ }
+
+ public void setHostedProfileServiceVersion(String hostedProfileServiceVersion) {
+ this.hostedProfileServiceVersion = hostedProfileServiceVersion;
+ }
+
+ public String getHashKey() {
+ return hashKey;
+ }
+
+ public void setHashKey(String hashKey) {
+ this.hashKey = hashKey;
+ }
+
+ public int getMinutesToExpiry() {
+ return minutesToExpiry;
+ }
+
+ public void setMinutesToExpiry(int minutesToExpiry) {
+ this.minutesToExpiry = minutesToExpiry;
+ }
+}
diff --git a/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/managment/BamboraCardService.java b/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/managment/BamboraCardService.java
new file mode 100644
index 0000000..75f03e9
--- /dev/null
+++ b/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/managment/BamboraCardService.java
@@ -0,0 +1,9 @@
+package ca.bc.gov.open.bambora.payment.starter.managment;
+
+import ca.bc.gov.open.bambora.payment.starter.managment.models.RecurringPaymentDetails;
+import com.sun.jndi.toolkit.url.Uri;
+
+public interface BamboraCardService {
+ Uri setupRecurringPayment(RecurringPaymentDetails recurringPaymentDetails);
+
+}
diff --git a/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/managment/BamboraCardServiceImpl.java b/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/managment/BamboraCardServiceImpl.java
new file mode 100644
index 0000000..8a46071
--- /dev/null
+++ b/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/managment/BamboraCardServiceImpl.java
@@ -0,0 +1,79 @@
+package ca.bc.gov.open.bambora.payment.starter.managment;
+
+import ca.bc.gov.open.bambora.payment.starter.BamboraConstants;
+import ca.bc.gov.open.bambora.payment.starter.BamboraException;
+import ca.bc.gov.open.bambora.payment.starter.BamboraProperties;
+import ca.bc.gov.open.bambora.payment.starter.managment.models.RecurringPaymentDetails;
+import com.sun.jndi.toolkit.url.Uri;
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.net.MalformedURLException;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+public class BamboraCardServiceImpl implements BamboraCardService {
+
+ private final BamboraProperties bamboraProperties;
+
+ public BamboraCardServiceImpl(BamboraProperties bamboraProperties) {
+ this.bamboraProperties = bamboraProperties;
+ }
+
+ @Override
+ public Uri setupRecurringPayment(RecurringPaymentDetails recurringPaymentDetails) {
+ try {
+ return buildRecurringPaymentUrl(recurringPaymentDetails);
+ } catch (MalformedURLException e) {
+ throw new BamboraException("Url construction failed", e.getCause());
+ }
+ }
+
+
+ private Uri buildRecurringPaymentUrl(RecurringPaymentDetails recurringPaymentDetails) throws MalformedURLException {
+
+ String operationType = (recurringPaymentDetails.getEndUserId() != null ? BamboraConstants.OperationTypes.M.toString() : BamboraConstants.OperationTypes.N.toString());
+
+ StringBuilder paramString = new StringBuilder();
+
+ paramString.append(formatBamboraParam("", BamboraConstants.PARAM_PPRDIR_SERVICE_VERSION, bamboraProperties.getHostedProfileServiceVersion()));
+
+ paramString.append(formatBamboraParam("&", BamboraConstants.PARAM_PPRDIR_MERCHANT_ID, bamboraProperties.getMerchantId()));
+
+ paramString.append(formatBamboraParam("&", BamboraConstants.PARAM_PPRDIR_LANGUAGE, BamboraConstants.LANGUAGE_TYPE));
+
+ paramString.append(formatBamboraParam("&", BamboraConstants.PARAM_PPRDIR_OPERATION_TYPE, operationType));
+
+ paramString.append(formatBamboraParam("&", BamboraConstants.PARAM_PPRDIR_REF1, recurringPaymentDetails.getEchoData()));
+
+ paramString.append(formatBamboraParam("&", BamboraConstants.PARAM_PPRDIR_RETURN_URL, recurringPaymentDetails.getRedirectURL()));
+
+ paramString.append(formatBamboraParam("&", BamboraConstants.PARAM_PPRDIR_ORDER_NUMBER, recurringPaymentDetails.getRedirectURL()));
+
+ if (operationType.equals(BamboraConstants.OperationTypes.M.toString()))
+ paramString.append(formatBamboraParam("&", BamboraConstants.PARAM_PPRDIR_CUSTOMER_CODE, recurringPaymentDetails.getEndUserId()));
+
+ paramString.append(MessageFormat.format("&{0}={1}&{2}={3}", BamboraConstants.PARAM_TRANS_HASH_VALUE, getHash(paramString.toString()), BamboraConstants.PARAM_TRANS_HASH_EXPIRY, getExpiry()));
+
+ return new Uri(MessageFormat.format("{0}?{1}", bamboraProperties.getHostedProfileUrl(), paramString.toString()));
+
+ }
+
+ private String getExpiry() {
+ SimpleDateFormat sdfDate = new SimpleDateFormat(BamboraConstants.PARAM_TRANS_HASH_EXPIRY_FORMAT);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(new Date());
+ cal.add(Calendar.MINUTE, bamboraProperties.getMinutesToExpiry());
+ return sdfDate.format(cal.getTime());
+ }
+
+ private String formatBamboraParam(String prefix, String key, String value) {
+ return MessageFormat.format("{0}{1}={2}", prefix, key, value).replace(" ", "%20");
+ }
+
+ private String getHash(String message) {
+ String digest = DigestUtils.md5Hex(MessageFormat.format("{0}{1}", message, bamboraProperties.getHashKey()));
+ return digest.toUpperCase();
+ }
+}
diff --git a/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/managment/models/RecurringPaymentDetails.java b/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/managment/models/RecurringPaymentDetails.java
new file mode 100644
index 0000000..d275fe5
--- /dev/null
+++ b/src/bambora-payment-starter/src/main/java/ca/bc/gov/open/bambora/payment/starter/managment/models/RecurringPaymentDetails.java
@@ -0,0 +1,68 @@
+package ca.bc.gov.open.bambora.payment.starter.managment.models;
+
+public class RecurringPaymentDetails {
+ private String orderNumber;
+ private String endUserId;
+ private String echoData;
+ private String redirectURL;
+
+ public String getOrderNumber() {
+ return orderNumber;
+ }
+
+ public String getEndUserId() {
+ return endUserId;
+ }
+
+ public String getEchoData() {
+ return echoData;
+ }
+
+ public String getRedirectURL() {
+ return redirectURL;
+ }
+
+ protected RecurringPaymentDetails(Builder builder) {
+ this.orderNumber = builder.orderNumber;
+ this.endUserId = builder.endUserId;
+ this.echoData = builder.echoData;
+ this.redirectURL = builder.redirectURL;
+ }
+
+ public static RecurringPaymentDetails.Builder builder() {
+ return new RecurringPaymentDetails.Builder();
+ }
+
+ public static class Builder {
+
+ private String orderNumber;
+ private String endUserId;
+ private String echoData;
+ private String redirectURL;
+
+ public Builder orderNumber(String orderNumber) {
+ this.orderNumber = orderNumber;
+ return this;
+ }
+
+ public Builder endUserId(String endUserId) {
+ this.endUserId = endUserId;
+ return this;
+ }
+
+ public Builder echoData(String echoData) {
+ this.echoData = echoData;
+ return this;
+ }
+
+ public Builder redirectURL(String redirectURL) {
+ this.redirectURL = redirectURL;
+ return this;
+ }
+
+ public RecurringPaymentDetails create() {
+ return new RecurringPaymentDetails(this);
+ }
+
+ }
+}
diff --git a/src/bambora-payment-starter/src/main/resources/META-INF/spring.factories b/src/bambora-payment-starter/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..84a80b3
--- /dev/null
+++ b/src/bambora-payment-starter/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ ca.bc.gov.open.bambora.payment.starter.AutoConfiguration
diff --git a/src/bambora-payment-starter/src/test/java/ca/bc/gov/open/bambora/payment/starter/AutoConfigurationTest.java b/src/bambora-payment-starter/src/test/java/ca/bc/gov/open/bambora/payment/starter/AutoConfigurationTest.java
new file mode 100644
index 0000000..82e463b
--- /dev/null
+++ b/src/bambora-payment-starter/src/test/java/ca/bc/gov/open/bambora/payment/starter/AutoConfigurationTest.java
@@ -0,0 +1,26 @@
+package ca.bc.gov.open.bambora.payment.starter;
+
+import ca.bc.gov.open.bambora.payment.starter.managment.BamboraCardService;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@DisplayName("Test AutoConfiguration")
+public class AutoConfigurationTest {
+ ApplicationContextRunner context = new ApplicationContextRunner();
+
+ @Test
+ @DisplayName("Test Beans Exist")
+ public void testConfigure() {
+
+ context.run(it -> {
+ Assertions.assertNotNull(assertThat(it).getBean(BamboraCardService.class));
+ });
+
+ }
+}
diff --git a/src/bambora-payment-starter/src/test/java/ca/bc/gov/open/bambora/payment/starter/managment/BamboraCardServiceImplTest.java b/src/bambora-payment-starter/src/test/java/ca/bc/gov/open/bambora/payment/starter/managment/BamboraCardServiceImplTest.java
new file mode 100644
index 0000000..f5faf43
--- /dev/null
+++ b/src/bambora-payment-starter/src/test/java/ca/bc/gov/open/bambora/payment/starter/managment/BamboraCardServiceImplTest.java
@@ -0,0 +1,79 @@
+package ca.bc.gov.open.bambora.payment.starter.managment;
+
+import ca.bc.gov.open.bambora.payment.starter.BamboraException;
+import ca.bc.gov.open.bambora.payment.starter.BamboraProperties;
+import ca.bc.gov.open.bambora.payment.starter.managment.models.RecurringPaymentDetails;
+import com.sun.jndi.toolkit.url.Uri;
+import org.junit.jupiter.api.*;
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@DisplayName("Test BamboraCardServiceImpl")
+public class BamboraCardServiceImplTest {
+ private static final String PROFILE_SERVICE_VERSION = "HOSTEDPROFILE";
+ private static final String HOSTED_PROFILE_URL = "http://localhost";
+ private static final String KEY = "XTN123TNV123";
+ private static final String MERCHANT_ID = "123";
+ private static final int MINUTES_TO_EXPIRY = 12;
+ private static final String ORDERNUM = "ORDERNUM";
+ private static final String ECHODATA = "ECHODATA";
+ private static final String REDIRECTURL = "REDIRECTURL";
+ private static final String END_USER_ID = "123";
+ private static final String BAMBORA_CLIENT_URL = "http://localhost?serviceVersion=HOSTEDPROFILE&merchantId=123&trnLanguage=eng&operationType=M&ref1=ECHODATA&trnReturnURL=REDIRECTURL&trnOrderNumber=REDIRECTURL&customerCode=123&hashValue=98C48E9A9C45B99698ED7D2ED7194A91";
+ private static final String BAMBORA_NEW_URL = "http://localhost?serviceVersion=HOSTEDPROFILE&merchantId=123&trnLanguage=eng&operationType=N&ref1=ECHODATA&trnReturnURL=REDIRECTURL&trnOrderNumber=REDIRECTURL&hashValue=BB2616ADA96F7C72824835D484F23B9B";
+
+ private BamboraCardServiceImpl sut;
+
+ private BamboraProperties bamboraProperties;
+
+ @BeforeEach
+ public void init() {
+
+ bamboraProperties = new BamboraProperties();
+ bamboraProperties.setHostedProfileServiceVersion(PROFILE_SERVICE_VERSION);
+ bamboraProperties.setHostedProfileUrl(HOSTED_PROFILE_URL);
+ bamboraProperties.setHashKey(KEY);
+ bamboraProperties.setMerchantId(MERCHANT_ID);
+ bamboraProperties.setMinutesToExpiry(MINUTES_TO_EXPIRY);
+
+ }
+
+ @Test
+ @DisplayName("With client create update url")
+ public void withClientIdCreateUpdateUrl() {
+ sut = new BamboraCardServiceImpl(bamboraProperties);
+
+ Uri actual = sut.setupRecurringPayment(createPaymentDetail(END_USER_ID));
+
+ Assertions.assertNotNull(actual);
+ Assertions.assertTrue(actual.toString().contains(BAMBORA_CLIENT_URL));
+ }
+
+
+ @Test
+ @DisplayName("Without client create update url")
+ public void withoutClientIdCreateUpdateUrl() {
+ sut = new BamboraCardServiceImpl(bamboraProperties);
+
+ Uri actual = sut.setupRecurringPayment(createPaymentDetail(null));
+
+ Assertions.assertNotNull(actual);
+ Assertions.assertTrue(actual.toString().contains(BAMBORA_NEW_URL));
+ }
+
+ @Test
+ @DisplayName("With invalid paramter throw invalid url exception")
+ public void withInvalidParamterThrowException() {
+ bamboraProperties.setHostedProfileUrl("NOTAURL");
+ sut = new BamboraCardServiceImpl(bamboraProperties);
+ Assertions.assertThrows(BamboraException.class, () -> sut.setupRecurringPayment(createPaymentDetail(END_USER_ID)));
+ }
+
+ private RecurringPaymentDetails createPaymentDetail(String endUserId) {
+ return RecurringPaymentDetails.builder()
+ .orderNumber(ORDERNUM)
+ .echoData(ECHODATA)
+ .redirectURL(REDIRECTURL)
+ .endUserId(endUserId)
+ .create();
+ }
+}
diff --git a/src/pom.xml b/src/pom.xml
index fd522dc..d4746d0 100644
--- a/src/pom.xml
+++ b/src/pom.xml
@@ -23,6 +23,7 @@
spring-bceid-starter
spring-starters-bom
spring-clamav-starter
+ bambora-payment-starter
@@ -50,6 +51,13 @@
+
+ payment
+
+ bambora-payment-starter
+
+
+