diff --git a/docker-compose.yml b/docker-compose.yml
index 49b5850..d82f197 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -16,7 +16,6 @@ services:
AWS_SECRET_ACCESS_KEY: "fakeaccesskey"
AWS_DYNAMODB_ENDPOINT: http://paymentsdb:54000
AWS_REGION: us-east-2
- ADMIN_ACCESS_TOKEN: token
MOCK_PAYMENT_PROVIDER: true
MP_TOKEN: token
MP_USER_ID: userId
diff --git a/pom.xml b/pom.xml
index 5f2ded1..5e77f1f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -83,6 +83,11 @@
spring-boot-starter-test
test
+
+ org.testcontainers
+ localstack
+ test
+
org.springframework.boot
spring-boot-starter-data-jpa
diff --git a/src/main/kotlin/com/fiap/payments/adapter/messaging/config/MessagingConfig.kt b/src/main/kotlin/com/fiap/payments/adapter/messaging/config/MessagingConfig.kt
index 1631848..768c35c 100644
--- a/src/main/kotlin/com/fiap/payments/adapter/messaging/config/MessagingConfig.kt
+++ b/src/main/kotlin/com/fiap/payments/adapter/messaging/config/MessagingConfig.kt
@@ -13,7 +13,7 @@ class MessagingConfig {
@Bean
fun createPaymentSender(snsTemplate: SnsTemplate,
- @Value("\${topic.response-payment}") topicName: String,
+ @Value("\${sns.topics.response-payment}") topicName: String,
objectMapper: ObjectMapper): PaymentSender {
return PaymentSenderImpl(snsTemplate, topicName, objectMapper)
}
diff --git a/src/main/kotlin/com/fiap/payments/config/JWTSecurityConfig.kt b/src/main/kotlin/com/fiap/payments/config/JWTSecurityConfig.kt
index 2e8c882..233283b 100644
--- a/src/main/kotlin/com/fiap/payments/config/JWTSecurityConfig.kt
+++ b/src/main/kotlin/com/fiap/payments/config/JWTSecurityConfig.kt
@@ -2,6 +2,7 @@ package com.fiap.payments.config
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
import io.swagger.v3.oas.annotations.security.SecurityScheme
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
@@ -19,6 +20,11 @@ import org.springframework.security.web.SecurityFilterChain
bearerFormat = "JWT",
scheme = "bearer"
)
+@ConditionalOnProperty(
+ value = ["security.enable"],
+ havingValue = "true",
+ matchIfMissing = true
+)
class JWTSecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
diff --git a/src/main/kotlin/com/fiap/payments/driver/database/config/DynamoDBConfig.kt b/src/main/kotlin/com/fiap/payments/driver/database/config/DynamoDBConfig.kt
index 2c005e8..c73e9c1 100644
--- a/src/main/kotlin/com/fiap/payments/driver/database/config/DynamoDBConfig.kt
+++ b/src/main/kotlin/com/fiap/payments/driver/database/config/DynamoDBConfig.kt
@@ -31,10 +31,10 @@ class DynamoDBConfig {
* For local run.
*/
@Bean("amazonDynamoDB")
- @ConditionalOnProperty("aws.dynamodb.local", havingValue = "true")
+ @ConditionalOnProperty("local", havingValue = "true")
fun amazonDynamoDB(
- @Value("\${aws.dynamodb.endpoint}") endpoint: String,
- @Value("\${aws.dynamodb.region}") region: String,
+ @Value("\${spring.cloud.aws.endpoint}") endpoint: String,
+ @Value("\${spring.cloud.aws.region.static}") region: String,
): AmazonDynamoDB {
return AmazonDynamoDBClientBuilder.standard()
// using default credentials provider chain, which searches for environment variables
@@ -53,7 +53,7 @@ class DynamoDBConfig {
* we need to keep as v1 and include token provider to the chain.
*/
@Bean("amazonDynamoDB")
- @ConditionalOnProperty("aws.dynamodb.local", havingValue = "false")
+ @ConditionalOnProperty("local", havingValue = "false")
fun awsCredentialsProvider(): AmazonDynamoDB {
return AmazonDynamoDBClientBuilder.standard()
// AWS_WEB_IDENTITY_TOKEN_FILE is present.
diff --git a/src/main/kotlin/com/fiap/payments/driver/messaging/consumer/RequestPaymentConsumer.kt b/src/main/kotlin/com/fiap/payments/driver/messaging/consumer/RequestPaymentConsumer.kt
index cfaea70..461e539 100644
--- a/src/main/kotlin/com/fiap/payments/driver/messaging/consumer/RequestPaymentConsumer.kt
+++ b/src/main/kotlin/com/fiap/payments/driver/messaging/consumer/RequestPaymentConsumer.kt
@@ -4,7 +4,6 @@ import com.fiap.payments.driver.messaging.event.PaymentRequestEvent
import com.fiap.payments.usecases.ProvidePaymentRequestUseCase
import io.awspring.cloud.sqs.annotation.SqsListener
import org.slf4j.LoggerFactory
-import org.springframework.context.event.EventListener
import org.springframework.messaging.MessageHeaders
import org.springframework.messaging.handler.annotation.Headers
import org.springframework.scheduling.annotation.EnableAsync
diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/src/main/resources/META-INF/additional-spring-configuration-metadata.json
index b3a87de..d6d6750 100644
--- a/src/main/resources/META-INF/additional-spring-configuration-metadata.json
+++ b/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -1,10 +1,5 @@
{
"properties": [
- {
- "name": "admin.access-token",
- "type": "java.lang.String",
- "description": "Description for admin-access-token."
- },
{
"name": "payment-provider.mock",
"type": "java.lang.String",
diff --git a/src/main/resources/application-live.yml b/src/main/resources/application-live.yml
new file mode 100644
index 0000000..a78ab76
--- /dev/null
+++ b/src/main/resources/application-live.yml
@@ -0,0 +1,10 @@
+security:
+ enable: true
+
+spring:
+ security:
+ oauth2:
+ resourceserver:
+ jwt:
+ issuer-uri: ${COGNITO_ISSUER_URI}
+ jwk-set-uri: ${COGNITO_JWK_SET_URI}
\ No newline at end of file
diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml
index 009f37f..607108d 100644
--- a/src/main/resources/application-local.yml
+++ b/src/main/resources/application-local.yml
@@ -1,8 +1,4 @@
-aws:
- dynamodb:
- local: true
- endpoint: ${AWS_DYNAMODB_ENDPOINT}
- region: ${AWS_REGION}
+local: true
payment-provider:
mock: true
@@ -21,9 +17,9 @@ sqs:
queues:
request-payment: request-payment_queue
-topic:
- response-payment: arn:aws:sns:us-east-2:000000000000:payment-response_topic
-
+sns:
+ topics:
+ response-payment: arn:aws:sns:us-east-2:000000000000:payment-response_topic
server:
port: 8081
diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml
index 89b6211..f005990 100644
--- a/src/main/resources/application-test.yml
+++ b/src/main/resources/application-test.yml
@@ -1,5 +1,4 @@
-admin:
- access-token: token
+local: true
payment-provider:
- mock: true
+ mock: true
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index f47a4cc..b376024 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,15 +1,6 @@
spring:
application:
name: payments
- security:
- oauth2:
- resourceserver:
- jwt:
- issuer-uri: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_ygM5FRn7D
- jwk-set-uri: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_ygM5FRn7D/.well-known/jwks.json
-
-admin:
- access-token: ${ADMIN_ACCESS_TOKEN}
payment-provider:
mock: ${MOCK_PAYMENT_PROVIDER}
@@ -23,9 +14,15 @@ mercadopago:
posId: ${MP_POS_ID}
webhookBaseUrl: ${MP_WEBHOOK_BASE_URL}
-aws:
- dynamodb:
- local: false
+local: false
+
+sqs:
+ queues:
+ request-payment: request-payment_queue
+
+sns:
+ topics:
+ response-payment: ${SNS_TOPIC_RESPONSE_PAYMENT_ARN}
clients:
orders-api:
@@ -33,3 +30,6 @@ clients:
server:
port: 8082
+
+security:
+ enable: false
\ No newline at end of file
diff --git a/src/test/kotlin/com/fiap/payments/TestAnnotations.kt b/src/test/kotlin/com/fiap/payments/TestAnnotations.kt
index 2f5b2e8..7836345 100644
--- a/src/test/kotlin/com/fiap/payments/TestAnnotations.kt
+++ b/src/test/kotlin/com/fiap/payments/TestAnnotations.kt
@@ -1,9 +1,17 @@
package com.fiap.payments
+import com.fiap.payments.it.LocalStackContainerInitializer
import org.junit.jupiter.api.Tag
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
import org.springframework.test.context.ActiveProfiles
+import org.springframework.test.context.ContextConfiguration
@Tag("IntegrationTest")
@ActiveProfiles("test")
@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE)
annotation class IntegrationTest
+
+@ContextConfiguration(initializers = [LocalStackContainerInitializer::class])
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE)
+annotation class WithLocalstack
\ No newline at end of file
diff --git a/src/test/kotlin/com/fiap/payments/it/BasicIntegrationTest.kt b/src/test/kotlin/com/fiap/payments/it/BasicIntegrationTest.kt
new file mode 100644
index 0000000..6a52904
--- /dev/null
+++ b/src/test/kotlin/com/fiap/payments/it/BasicIntegrationTest.kt
@@ -0,0 +1,15 @@
+package com.fiap.payments.it
+
+import com.fiap.payments.IntegrationTest
+import com.fiap.payments.WithLocalstack
+import org.junit.jupiter.api.Test
+import org.springframework.boot.test.context.SpringBootTest
+
+@IntegrationTest
+@WithLocalstack
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+class BasicIntegrationTest {
+
+ @Test
+ fun contextLoads() { }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/fiap/payments/it/LocalstackContainerInitializer.kt b/src/test/kotlin/com/fiap/payments/it/LocalstackContainerInitializer.kt
new file mode 100644
index 0000000..a4e943b
--- /dev/null
+++ b/src/test/kotlin/com/fiap/payments/it/LocalstackContainerInitializer.kt
@@ -0,0 +1,145 @@
+package com.fiap.payments.it
+
+import org.springframework.boot.test.util.TestPropertyValues
+import org.springframework.context.ApplicationContextInitializer
+import org.springframework.context.ConfigurableApplicationContext
+import org.testcontainers.containers.localstack.LocalStackContainer
+import org.testcontainers.containers.wait.strategy.Wait.forListeningPort
+import org.testcontainers.utility.DockerImageName
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
+import software.amazon.awssdk.regions.Region
+import software.amazon.awssdk.services.sns.SnsClient
+import software.amazon.awssdk.services.sns.model.CreateTopicRequest
+import software.amazon.awssdk.services.sns.model.SubscribeRequest
+import software.amazon.awssdk.services.sqs.SqsClient
+import software.amazon.awssdk.services.sqs.model.CreateQueueRequest
+import software.amazon.awssdk.services.sqs.model.GetQueueAttributesRequest
+import software.amazon.awssdk.services.sqs.model.QueueAttributeName
+
+class LocalStackContainerInitializer :
+ ApplicationContextInitializer,
+ LocalStackContainer(
+ DockerImageName.parse("localstack/localstack")
+ ) {
+
+ companion object {
+ private val instance: LocalStackContainer = LocalStackContainerInitializer()
+ .withServices(
+ Service.DYNAMODB,
+ Service.SQS,
+ Service.SNS,
+ )
+ .withEnv(
+ mapOf(
+ "DEBUG" to "1",
+ "DEFAULT_REGION" to "us-east-1",
+ )
+ )
+ .waitingFor(forListeningPort())
+ }
+
+ override fun initialize(configurableApplicationContext: ConfigurableApplicationContext) {
+ instance.start()
+
+ val awsCredentials = AwsBasicCredentials.create(
+ instance.accessKey,
+ instance.secretKey
+ )
+
+ val region = Region.of(instance.region)
+
+ val sqsClient = SqsClient.builder()
+ .endpointOverride(instance.getEndpointOverride(Service.SQS))
+ .region(region)
+ .credentialsProvider(StaticCredentialsProvider.create(awsCredentials))
+ .build()
+
+ val snsClient = SnsClient.builder()
+ .endpointOverride(instance.getEndpointOverride(Service.SNS))
+ .region(region)
+ .credentialsProvider(StaticCredentialsProvider.create(awsCredentials))
+ .build()
+
+ createQueues(sqsClient)
+ createTopics(snsClient)
+ subscribeToTopics(snsClient, sqsClient)
+
+ TestPropertyValues.of(
+ "spring.cloud.aws.endpoint=${instance.endpoint}",
+ "spring.cloud.aws.credentials.access-key=${instance.accessKey}",
+ "spring.cloud.aws.credentials.secret-key=${instance.secretKey}",
+ "spring.cloud.aws.region.static=${instance.region}",
+ "sns.topics.response-payment=${getTopicArn(snsClient, "payment-response_topic")}",
+ ).applyTo(configurableApplicationContext)
+ }
+
+ private fun createQueues(sqsClient: SqsClient) {
+ val queues = listOf("request-payment_queue", "payment-response_queue")
+
+ queues.forEach { queue ->
+ val dlqName = "${queue}_dlq"
+ sqsClient.createQueue(CreateQueueRequest.builder().queueName(dlqName).build())
+ println("DLQ queue [$dlqName] created")
+
+ val dlqArn = getQueueArn(sqsClient, dlqName)
+
+ val redrivePolicy = """{"deadLetterTargetArn":"$dlqArn","maxReceiveCount":"3"}"""
+
+ sqsClient.createQueue(
+ CreateQueueRequest.builder()
+ .queueName(queue)
+ .attributes(
+ mapOf(
+ QueueAttributeName.DELAY_SECONDS to "5",
+ QueueAttributeName.REDRIVE_POLICY to redrivePolicy
+ )
+ )
+ .build()
+ )
+ println("Queue [$queue] created")
+ }
+ }
+
+ private fun createTopics(snsClient: SnsClient) {
+ val topics = listOf("request-payment_topic", "payment-response_topic")
+
+ topics.forEach { topic ->
+ snsClient.createTopic(CreateTopicRequest.builder().name(topic).build())
+ println("Topic [$topic] created")
+ }
+ }
+
+ private fun getQueueArn(sqsClient: SqsClient, queueName: String) =
+ sqsClient.getQueueAttributes(
+ GetQueueAttributesRequest.builder()
+ .queueUrl(sqsClient.getQueueUrl { it.queueName(queueName) }.queueUrl())
+ .attributeNames(QueueAttributeName.QUEUE_ARN)
+ .build()
+ ).attributes()[QueueAttributeName.QUEUE_ARN]
+
+ private fun getTopicArn(snsClient: SnsClient, topicName: String) =
+ snsClient.createTopic { it.name(topicName) }.topicArn()
+
+ private fun subscribeToTopics(snsClient: SnsClient, sqsClient: SqsClient) {
+ val subscriptions = listOf(
+ Pair("request-payment_topic", "request-payment_queue"),
+ Pair("payment-response_topic", "payment-response_queue")
+ )
+
+ subscriptions.forEach { (topicName, queueName) ->
+ val topicArn = getTopicArn(snsClient, topicName)
+ val queueArn = getQueueArn(sqsClient, queueName)
+
+ snsClient.subscribe(
+ SubscribeRequest.builder()
+ .topicArn(topicArn)
+ .protocol("sqs")
+ .endpoint(queueArn)
+ .attributes(mapOf("RawMessageDelivery" to "true"))
+ .build()
+ )
+ println("Queue [$queueName] subscribed to Topic [$topicName]")
+ }
+ }
+}
diff --git a/terraform/secrets.tf b/terraform/secrets.tf
index 78473ce..b3c7f47 100644
--- a/terraform/secrets.tf
+++ b/terraform/secrets.tf
@@ -9,10 +9,10 @@ module "secrets_manager" {
# For security reasons, insert values manually after apply
secret_string = jsonencode({
- token = null
- userId = null
- posId = null
- webhookBaseUrl = null
+ token = ""
+ userId = ""
+ posId = ""
+ webhookBaseUrl = ""
})
tags = var.tags
diff --git a/terraform/sns.tf b/terraform/sns.tf
new file mode 100644
index 0000000..f8c3f53
--- /dev/null
+++ b/terraform/sns.tf
@@ -0,0 +1,23 @@
+resource "aws_sns_topic" "request_payment_topic" {
+ name = "request-payment_topic"
+}
+
+resource "aws_sns_topic" "payment_response_topic" {
+ name = "payment-response_topic"
+}
+
+resource "aws_sns_topic_subscription" "request_payment_subscription" {
+ topic_arn = aws_sns_topic.request_payment_topic.arn
+ protocol = "sqs"
+ endpoint = aws_sqs_queue.request_payment_queue.arn
+
+ raw_message_delivery = true
+}
+
+resource "aws_sns_topic_subscription" "payment_response_subscription" {
+ topic_arn = aws_sns_topic.payment_response_topic.arn
+ protocol = "sqs"
+ endpoint = aws_sqs_queue.payment_response_queue.arn
+
+ raw_message_delivery = true
+}
\ No newline at end of file
diff --git a/terraform/sqs.tf b/terraform/sqs.tf
new file mode 100644
index 0000000..6c77a9d
--- /dev/null
+++ b/terraform/sqs.tf
@@ -0,0 +1,29 @@
+resource "aws_sqs_queue" "request_payment_dlq" {
+ name = "request-payment_queue_dlq"
+}
+
+resource "aws_sqs_queue" "payment_response_dlq" {
+ name = "payment-response_queue_dlq"
+}
+
+resource "aws_sqs_queue" "request_payment_queue" {
+ name = "request-payment_queue"
+
+ redrive_policy = jsonencode({
+ deadLetterTargetArn = aws_sqs_queue.request_payment_dlq.arn
+ maxReceiveCount = 3
+ })
+
+ delay_seconds = 5
+}
+
+resource "aws_sqs_queue" "payment_response_queue" {
+ name = "payment-response_queue"
+
+ redrive_policy = jsonencode({
+ deadLetterTargetArn = aws_sqs_queue.payment_response_dlq.arn
+ maxReceiveCount = 3
+ })
+
+ delay_seconds = 5
+}