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} + ${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 + + +