AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: Validates mobile purchases
Parameters:
  MobileAccountId:
    Type: AWS::SSM::Parameter::Value<String>
    Default: 'mobileAccountId'
  MembershipAccountId:
    Type: AWS::SSM::Parameter::Value<String>
    Default: 'membershipAccountId'
  Stack:
    Description: Stack name
    Type: String
  App:
    Description: Application name
    Type: String
  Stage:
    Description: Stage name
    Type: String
    AllowedValues:
      - CODE
      - PROD
  DeployBucket:
    Description: Bucket where RiffRaff uploads artifacts on deploy
    Type: String
  HostedZoneId:
    Description: HostedZoneId
    Type: String
  HostedZoneName:
    Description: HostedZoneName
    Type: String
  ApiCertArn:
    Description: ACM Certificate for api use
    Type: String
  AppCertArn:
    Description: ACM Certificate for app use
    Type: String
  AppDNS:
    Description: DNS used by app
    Type: String
  GooglePubSubSecret:
    Type: String
    Description: The secret used by google's pubsub
    NoEcho: true
  ApplePubSubSecret:
    Type: String
    Description: The secret used by apple's pubsub
    NoEcho: true
  FeastApplePubSubSecret:
    Type: String
    Description: The secret used by apple's pubsub for the feast app
    NoEcho: true
  FeastGooglePubSubSecret:
    Type: String
    Description: The secret used by google's pubsub for the feast app
    NoEcho: true
  AlarmTopic:
    Type: String
    Description: The ARN of the SNS topic to send all the cloudwatch alarms to

Mappings:
  StageVariables:
    CODE:
      Schedule: 'rate(365 days)'
      AlarmActionsEnabled: FALSE
      SoftOptInConsentSetterStage: CODE
    PROD:
      Schedule: 'rate(30 minutes)'
      AlarmActionsEnabled: TRUE
      SoftOptInConsentSetterStage: PROD

Conditions:
  IsCode: !Equals [!Ref "Stage", "CODE"]
  IsProd: !Equals [!Ref "Stage", "PROD"]

Resources:
  MobilePurchasesLambdasRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: logs
          PolicyDocument:
            Statement:
              Effect: Allow
              Action:
                - logs:CreateLogGroup
                - logs:CreateLogStream
                - logs:PutLogEvents
                - cloudwatch:putMetricData
              Resource: "*"
        - PolicyName: config
          PolicyDocument:
            Statement:
              Action:
                - ssm:GetParametersByPath
              Effect: Allow
              Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${App}/${Stage}/${Stack}/*
        - PolicyName: iosuserpurchases-config
          PolicyDocument:
            Statement:
              Action:
                - ssm:GetParametersByPath
              Effect: Allow
              Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${App}-iosuserpurchases/${Stage}/${Stack}
        - PolicyName: iosvalidatereceipts-config
          PolicyDocument:
            Statement:
              Action:
                - ssm:GetParametersByPath
              Effect: Allow
              Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${App}-iosvalidatereceipts/${Stage}/${Stack}
        - PolicyName: googleoauth-config
          PolicyDocument:
            Statement:
              Action:
                - ssm:GetParametersByPath
              Effect: Allow
              Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${App}/${Stage}/google-oauth-lambda
        - PolicyName: google-access-tokens
          PolicyDocument:
            Statement:
              Action:
                - s3:GetObject
                - s3:PutObject
              Effect: Allow
              Resource: !Sub arn:aws:s3:::gu-mobile-access-tokens/${Stage}/google-play-developer-api/*
        - PolicyName: dynamo
          PolicyDocument:
            Statement:
              Effect: Allow
              Action:
                - "dynamodb:GetItem"
                - "dynamodb:BatchGetItem"
                - "dynamodb:BatchWriteItem"
                - "dynamodb:PutItem"
                - "dynamodb:UpdateItem"
                - "dynamodb:Query"
              Resource:
                - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-${Stack}-user-purchases
                - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscription-events
                - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscription-events-v2
                - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions
                - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions-parallel-test
                - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-user-subscriptions

        - PolicyName: Sqs
          PolicyDocument:
            Statement:
              Effect: Allow
              Action: sqs:*
              Resource:
                - !GetAtt GoogleSubscriptionsQueue.Arn
                - !GetAtt AppleSubscriptionsQueue.Arn
                - !GetAtt FeastAppleSubscriptionsQueue.Arn
                - !GetAtt FeastGoogleSubscriptionsQueue.Arn
                - !Sub arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:${App}-${Stage}-apple-historical-subscriptions
                - !Sub arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:${App}-${Stage}-google-historical-subscriptions
        - PolicyName: Kms
          PolicyDocument:
            Statement:
              Effect: Allow
              Action: ["kms:GenerateDataKey", "kms:Decrypt"]
              Resource:
                - !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/0215d06c-81c4-4896-a5da-c818770ea8db

  LogGroupValidateReceipts:
    Type: "AWS::Logs::LogGroup"
    Properties:
      LogGroupName: !Sub /aws/lambda/${App}-iosvalidatereceipts-${Stage}
      RetentionInDays: 7
  LogGroupUserPurchases:
    Type: "AWS::Logs::LogGroup"
    Properties:
      LogGroupName: !Sub /aws/lambda/${App}-iosuserpurchases-${Stage}
      RetentionInDays: 7
  MobilePuchasesApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Stage
      MethodSettings:
        [
          {
            "MetricsEnabled": True,
            "LoggingLevel": "OFF",
            "ResourcePath": "/*",
            "HttpMethod": "*",
          },
        ]
      DefinitionBody:
        swagger: "2.0"
        info:
          version: "1.0.0"
          title: !Sub ${App}-${Stage}
        paths:
          "/google/pubsub":
            post:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GooglePubSubLambda.Arn}/invocations
              consumes: [application/json]
              produces: [application/json]
              responses:
                "200":
                  "description": "Successfully processed a Live App purchase receipt from Google Play Store"
          "/google/subscription/{subscriptionId}/status":
            get:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GooglePlaySubStatusLambda.Arn}/invocations
              consumes: [application/json]
              produces: [application/json]
              responses:
                "200":
                  "description": "200 response"
          "/apple/subscription/status":
            post:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AppleSubStatusLambda.Arn}/invocations
              consumes: [application/json]
              produces: [application/json]
              responses:
                "200":
                  "description": "200 response"
          "/apple/pubsub":
            post:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApplePubSubLambda.Arn}/invocations
              consumes: [application/json]
              produces: [application/json]
              responses:
                "200":
                  "description": "Successfully processed a Live App purchase receipt from the Apple App Store"
          "/apple/linkToSubscriptions":
            post:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AppleLinkUserToSubLambda.Arn}/invocations
              consumes: [application/json]
              produces: [application/json]
              responses:
                "200":
                  "description": "Successfully handled link request from iOS Live App"
          "/google/linkToSubscriptions":
            post:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GoogleLinkUserToSubLambda.Arn}/invocations
              consumes: [application/json]
              produces: [application/json]
              responses:
                "200":
                  "description": "Successfully handled link request from Android Live App"
          "/user/subscriptions/{userId}":
            get:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${UserSubscriptionsLambda.Arn}/invocations
              consumes: [application/json]
              produces: [application/json]
              responses:
                "200":
                  "description": "200 response"
          "/apple/fetchOfferDetails":
            post:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AppleFetchOfferDetailsLambda.Arn}/invocations
              consumes: [application/json]
              produces: [application/json]
              responses:
                "200":
                  "description": "200 response (+)"
          "feast/apple/pubsub":
            post:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${FeastApplePubSubLambda.Arn}/invocations
              consumes: [application/json]
              produces: [application/json]
              responses:
                "200":
                  "description": "Successfully processed a Feast App purchase receipt from the Apple App Store"
          "/feast/google/pubsub":
            post:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${FeastGooglePubSubLambda.Arn}/invocations
              consumes: [application/json]
              produces: [application/json]
              responses:
                "200":
                  "description": "Successfully processed a Feast App purchase receipt from the Google Play Store"
          "/healthcheck":
            get:
              responses:
                "200":
                  description: "200 response"
              x-amazon-apigateway-integration:
                type: mock
                requestTemplates:
                  application/json: |
                    {
                      "statusCode" : 200
                    }
                httpMethod: GET
                responses:
                  default:
                    statusCode: "200"

  ApiDomainName:
    Type: AWS::ApiGateway::DomainName
    Properties:
      CertificateArn: !Ref ApiCertArn
      DomainName: !Sub ${App}.${HostedZoneName}

  AppDomainName:
    Type: AWS::ApiGateway::DomainName
    Properties:
      CertificateArn: !Ref AppCertArn
      DomainName: !Ref AppDNS

  ApiRoute53:
    Type: AWS::Route53::RecordSetGroup
    Properties:
      HostedZoneId: !Ref HostedZoneId
      RecordSets:
        - Name: !Ref ApiDomainName
          Type: A
          AliasTarget:
            HostedZoneId: Z2FDTNDATAQYW2
            DNSName: !GetAtt
              - ApiDomainName
              - DistributionDomainName

  ApiMapping:
    Type: AWS::ApiGateway::BasePathMapping
    Properties:
      DomainName: !Ref ApiDomainName
      RestApiId: !Ref MobilePuchasesApi
      Stage: !Ref Stage

  AppMapping:
    Type: AWS::ApiGateway::BasePathMapping
    Properties:
      DomainName: !Ref AppDomainName
      RestApiId: !Ref MobilePuchasesApi
      Stage: !Ref Stage

  GoogleOAuthLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: com.gu.mobilepurchases.googleoauth.lambda.GoogleOAuth::handler
      Runtime: java8
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-google-oauth/${App}-google-oauth.jar
      FunctionName: !Sub ${App}-googleoauth-${Stage}
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Environment:
        Variables:
          Stage: !Ref Stage
          Stack: !Ref Stack
          App: !Ref App
      Description: Fetches access tokens for the Google Play Developer API
      MemorySize: 512
      Timeout: 45
      Events:
        Schedule:
          Type: Schedule
          Properties:
            Schedule: rate(15 minutes)
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App

  GooglePubSubLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: google-pubsub.handler
      Runtime: nodejs20.x
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-google-pubsub/google-pubsub.zip
      FunctionName: !Sub ${App}-googlepubsub-${Stage}
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Environment:
        Variables:
          Stage: !Ref Stage
          Stack: !Ref Stack
          App: !Ref App
          Secret: !Ref GooglePubSubSecret
          QueueUrl: !Ref GoogleSubscriptionsQueue
      Description: Records play store events
      MemorySize: 128
      Timeout: 29
      Events:
        PostApi:
          Type: Api
          Properties:
            Path: "/google/pubsub"
            Method: POST
            RestApiId: !Ref MobilePuchasesApi
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App

  ApplePubSubLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: apple-pubsub.handler
      Runtime: nodejs20.x
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-apple-pubsub/apple-pubsub.zip
      FunctionName: !Sub ${App}-applepubsub-${Stage}
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Environment:
        Variables:
          Stage: !Ref Stage
          Stack: !Ref Stack
          App: !Ref App
          Secret: !Ref ApplePubSubSecret
          QueueUrl: !Ref AppleSubscriptionsQueue
      Description: Records play store events
      MemorySize: 128
      Timeout: 29
      Events:
        PostApi:
          Type: Api
          Properties:
            Path: "/apple/pubsub"
            Method: POST
            RestApiId: !Ref MobilePuchasesApi
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App

  GoogleLinkUserToSubLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: google-link-user-subscription.handler
      Runtime: nodejs20.x
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-google-link-user-subscription/google-link-user-subscription.zip
      FunctionName: !Sub ${App}-google-link-user-subscription-${Stage}
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Environment:
        Variables:
          Stage: !Ref Stage
          Stack: !Ref Stack
          App: !Ref App
          QueueUrl: !Ref GoogleSubscriptionsQueue
      Description: Links users to subscriptions
      MemorySize: 128
      Timeout: 29
      Events:
        PostApi:
          Type: Api
          Properties:
            Path: "/google/linkToSubscriptions"
            Method: POST
            RestApiId: !Ref MobilePuchasesApi
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App

  AppleLinkUserToSubLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: apple-link-user-subscription.handler
      Runtime: nodejs20.x
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-apple-link-user-subscription/apple-link-user-subscription.zip
      FunctionName: !Sub ${App}-apple-link-user-subscription-${Stage}
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Environment:
        Variables:
          Stage: !Ref Stage
          Stack: !Ref Stack
          App: !Ref App
          QueueUrl: !Ref AppleSubscriptionsQueue
      Description: Links users to subscriptions
      MemorySize: 128
      Timeout: 29
      Events:
        PostApi:
          Type: Api
          Properties:
            Path: "/apple/linkToSubscriptions"
            Method: POST
            RestApiId: !Ref MobilePuchasesApi
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App

  GooglePlaySubStatusLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: google-subscription-status.handler
      Runtime: nodejs20.x
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-google-subscription-status/google-subscription-status.zip
      FunctionName: !Sub ${App}-google-subscription-status-${Stage}
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Environment:
        Variables:
          Stage: !Ref Stage
          Stack: !Ref Stack
          App: !Ref App
      Description: Checks the status of a Play Store subscription using the Google Play Developer API
      MemorySize: 128
      Timeout: 29
      Events:
        PostApi:
          Type: Api
          Properties:
            Path: "/google/subscription/{subscriptionId}/status"
            Method: GET
            RestApiId: !Ref MobilePuchasesApi
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App

  AppleSubStatusLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: apple-subscription-status.handler
      Runtime: nodejs20.x
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-apple-subscription-status/apple-subscription-status.zip
      FunctionName: !Sub ${App}-apple-subscription-status-${Stage}
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Environment:
        Variables:
          Stage: !Ref Stage
          Stack: !Ref Stack
          App: !Ref App
      Description: Checks the status of an Apple App store subscription using the apple API
      MemorySize: 128
      Timeout: 29
      Events:
        PostApi:
          Type: Api
          Properties:
            Path: "/apple/subscription/status"
            Method: POST
            RestApiId: !Ref MobilePuchasesApi
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App

  AppleFetchOfferDetailsLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: apple-fetch-offer-details.handler
      Runtime: nodejs20.x
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-apple-fetch-offer-details/apple-fetch-offer-details.zip
      FunctionName: !Sub ${App}-apple-fetch-offer-details-${Stage}
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Environment:
        Variables:
          Stage: !Ref Stage
          Stack: !Ref Stack
          App: !Ref App
      Description: apple fetch offer details lambda (+)
      MemorySize: 128
      Timeout: 29
      Events:
        PostApi:
          Type: Api
          Properties:
            Path: "/apple/fetchOfferDetails"
            Method: POST
            RestApiId: !Ref MobilePuchasesApi
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App

  FeastGooglePubSubLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: feast-google-pubsub.handler
      Runtime: nodejs20.x
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-feast-google-pubsub/feast-google-pubsub.zip
      FunctionName: !Sub ${App}-feastgooglepubsub-${Stage}
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Environment:
        Variables:
          Stage: !Ref Stage
          Stack: !Ref Stack
          App: !Ref App
          Secret: !Ref FeastGooglePubSubSecret
          QueueUrl: !Ref FeastGoogleSubscriptionsQueue
      Description: Records Google play store events for Feast app
      MemorySize: 128
      Timeout: 29
      Events:
        PostApi:
          Type: Api
          Properties:
            Path: "/feast/google/pubsub"
            Method: POST
            RestApiId: !Ref MobilePuchasesApi
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App

  FeastApplePubSubLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: feast-apple-pubsub.handler
      Runtime: nodejs20.x
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-feast-apple-pubsub/feast-apple-pubsub.zip
      FunctionName: !Sub ${App}-feastapplepubsub-${Stage}
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Environment:
        Variables:
          Stage: !Ref Stage
          Stack: !Ref Stack
          App: !Ref App
          QueueUrl: !Ref FeastAppleSubscriptionsQueue
          Secret: !Ref FeastApplePubSubSecret
      Description: Records app store events for Feast app
      MemorySize: 128
      Timeout: 29
      Events:
        PostApi:
          Type: Api
          Properties:
            Path: "/feast/apple/pubsub"
            Method: POST
            RestApiId: !Ref MobilePuchasesApi
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App

  GoogleSubscriptionsQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub ${App}-${Stage}-google-subscriptions-to-fetch
      RedrivePolicy:
        deadLetterTargetArn: !GetAtt GoogleSubscriptionsQueueDlq.Arn
        maxReceiveCount: 8
      KmsMasterKeyId: alias/aws/sqs
      Tags:
        - Key: Stage
          Value: !Ref Stage
        - Key: Stack
          Value: !Ref Stack
        - Key: App
          Value: !Ref App

  GoogleSubscriptionsQueueDlq:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub ${App}-${Stage}-google-subscriptions-to-fetch-dlq
      KmsMasterKeyId: alias/aws/sqs
      Tags:
        - Key: Stage
          Value: !Ref Stage
        - Key: Stack
          Value: !Ref Stack
        - Key: App
          Value: !Ref App

  AppleSubscriptionsQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub ${App}-${Stage}-apple-subscriptions-to-fetch
      RedrivePolicy:
        deadLetterTargetArn: !GetAtt AppleSubscriptionsQueueDlq.Arn
        maxReceiveCount: 8
      KmsMasterKeyId: alias/aws/sqs
      Tags:
        - Key: Stage
          Value: !Ref Stage
        - Key: Stack
          Value: !Ref Stack
        - Key: App
          Value: !Ref App

  AppleSubscriptionsQueueDlq:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub ${App}-${Stage}-apple-subscriptions-to-fetch-dlq
      KmsMasterKeyId: alias/aws/sqs
      Tags:
        - Key: Stage
          Value: !Ref Stage
        - Key: Stack
          Value: !Ref Stack
        - Key: App
          Value: !Ref App

  FeastAppleSubscriptionsQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub ${App}-${Stage}-feast-apple-subscriptions-to-fetch
      RedrivePolicy:
        deadLetterTargetArn: !GetAtt FeastAppleSubscriptionsQueueDlq.Arn
        maxReceiveCount: 8
      KmsMasterKeyId: alias/aws/sqs
      Tags:
        - Key: Stage
          Value: !Ref Stage
        - Key: Stack
          Value: !Ref Stack
        - Key: App
          Value: !Ref App

  FeastAppleSubscriptionsQueueDlq:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub ${App}-${Stage}-feast-apple-subscriptions-to-fetch-dlq
      KmsMasterKeyId: alias/aws/sqs
      Tags:
        - Key: Stage
          Value: !Ref Stage
        - Key: Stack
          Value: !Ref Stack
        - Key: App
          Value: !Ref App

  FeastGoogleSubscriptionsQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub ${App}-${Stage}-feast-google-subscriptions-to-fetch
      RedrivePolicy:
        deadLetterTargetArn: !GetAtt FeastGoogleSubscriptionsQueueDlq.Arn
        maxReceiveCount: 8
      KmsMasterKeyId: alias/aws/sqs
      Tags:
        - Key: Stage
          Value: !Ref Stage
        - Key: Stack
          Value: !Ref Stack
        - Key: App
          Value: !Ref App

  FeastGoogleSubscriptionsQueueDlq:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub ${App}-${Stage}-feast-google-subscriptions-to-fetch-dlq
      KmsMasterKeyId: alias/aws/sqs
      Tags:
        - Key: Stage
          Value: !Ref Stage
        - Key: Stack
          Value: !Ref Stack
        - Key: App
          Value: !Ref App

  GoogleTokenRefreshFailureAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled:
        !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled]
      AlarmActions:
        - Ref: AlarmTopic
      OKActions:
        - Ref: AlarmTopic
      AlarmName: !Sub mobile-purchases-${Stage}-google-oauth-token-refresh-failure
      AlarmDescription: !Sub Trigger the GoogleOAuth lambda manually to refresh the token
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Dimensions:
        - Name: FunctionName
          Value: !Ref GoogleOAuthLambda
      EvaluationPeriods: 1
      MetricName: Errors
      Namespace: AWS/Lambda
      Period: 60
      Statistic: Sum
      Threshold: 1
      TreatMissingData: notBreaching
      Tags:
        - Key: App
          Value: mobile-purchases-google-oauth

  GooglePlaySubsStatus5xxErrors:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled:
        !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled]
      AlarmActions:
        - Ref: AlarmTopic
      OKActions:
        - Ref: AlarmTopic
      AlarmName: !Sub mobile-purchases-${Stage}-play-subscription-status-check-errors
      AlarmDescription: |
        More than 20% of attempts to check Play subscription status resulted in a 5XX error.
        Runbook: https://docs.google.com/document/d/1OwNDf_xSK3hhq1K0DP_4Uq7vIBFgfvlkd4zfiRZDczw/edit#heading=h.cnuchxbdu0tl
      Metrics:
        - Id: e1
          Label: Percentage of requests which result in a 5XX error
          Expression: "100*(FILL(m1,0)/FILL(m2,1))"
        - Id: m1
          Label: Number of 5XX responses
          MetricStat:
            Metric:
              MetricName: 5XXError
              Namespace: AWS/ApiGateway
              Dimensions:
                - Name: ApiName
                  Value: !Sub ${App}-${Stage}
                - Name: Method
                  Value: GET
                - Name: Resource
                  Value: /google/subscription/{subscriptionId}/status
                - Name: Stage
                  Value: !Ref Stage
            Period: 600
            Stat: Sum
          ReturnData: false
        - Id: m2
          Label: Total number of requests
          MetricStat:
            Metric:
              MetricName: Count
              Namespace: AWS/ApiGateway
              Dimensions:
                - Name: ApiName
                  Value: !Sub ${App}-${Stage}
                - Name: Method
                  Value: GET
                - Name: Resource
                  Value: /google/subscription/{subscriptionId}/status
                - Name: Stage
                  Value: !Ref Stage
            Period: 600
            Stat: Sum
          ReturnData: false
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      Threshold: 20
      Tags:
        - Key: App
          Value: mobile-purchases-google-subscription-status

  UpdateGoogleSubscriptionsLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${App}-google-update-subscriptions-${Stage}
      Code:
        S3Bucket: !Ref DeployBucket
        S3Key: !Sub ${Stack}/${Stage}/${App}-google-update-subscriptions/google-update-subscriptions.zip
      Environment:
        Variables:
          App: !Sub ${App}
          Stack: !Sub ${Stack}
          Stage: !Sub ${Stage}
          HistoricalQueueUrl: !Sub https://sqs.${AWS::Region}.amazonaws.com/${AWS::AccountId}/${App}-${Stage}-google-historical-subscriptions
      Description: Consomes subscription data updates from google playstore from sqs and stores them in dynamo
      Handler: google-update-subscriptions.handler
      MemorySize: 512
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Timeout: 25
      Runtime: nodejs20.x

  UpdateGoogleSubscriptionsEventSource:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      FunctionName: !Ref UpdateGoogleSubscriptionsLambda
      Enabled: true
      EventSourceArn: !GetAtt GoogleSubscriptionsQueue.Arn
      BatchSize: 1

  AppleSubsStatus5xxErrors:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled:
        !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled]
      AlarmActions:
        - Ref: AlarmTopic
      AlarmName: !Sub mobile-purchases-${Stage}-apple-subscription-status-check-errors
      AlarmDescription: |
        More than 10% of attempts to check Apple subscription status resulted in a 5XX error over a 20min period.
      Metrics:
        - Id: e1
          Label: Percentage of requests which result in a 5XX error
          Expression: "100*(FILL(m1,0)/FILL(m2,1))"
        - Id: m1
          Label: Number of 5XX responses
          MetricStat:
            Metric:
              MetricName: 5XXError
              Namespace: AWS/ApiGateway
              Dimensions:
                - Name: ApiName
                  Value: !Sub ${App}-${Stage}
                - Name: Method
                  Value: POST
                - Name: Resource
                  Value: /apple/subscription/status
                - Name: Stage
                  Value: !Ref Stage
            Period: 1200
            Stat: Sum
          ReturnData: false
        - Id: m2
          Label: Total number of requests
          MetricStat:
            Metric:
              MetricName: Count
              Namespace: AWS/ApiGateway
              Dimensions:
                - Name: ApiName
                  Value: !Sub ${App}-${Stage}
                - Name: Method
                  Value: POST
                - Name: Resource
                  Value: /apple/subscription/status
                - Name: Stage
                  Value: !Ref Stage
            Period: 1200
            Stat: Sum
          ReturnData: false
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      Threshold: 10
      Tags:
        - Key: App
          Value: mobile-purchases-apple-subscription-status

  ApplePubsub5xxErrors:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled:
        !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled]
      AlarmActions:
        - Ref: AlarmTopic
      OKActions:
        - Ref: AlarmTopic
      AlarmName: !Sub mobile-purchases-${Stage}-apple-pubsub-check-errors
      AlarmDescription: Two HTTP requests to the iOS pubsub endpoint resulted in 5XX errors.
      Dimensions:
        - Name: ApiName
          Value: !Sub ${App}-${Stage}
        - Name: Method
          Value: POST
        - Name: Resource
          Value: /apple/pubsub
        - Name: Stage
          Value: !Ref Stage
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: 5XXError
      Namespace: AWS/ApiGateway
      Period: 300
      Statistic: Sum
      Threshold: 2
      TreatMissingData: notBreaching
      Tags:
        - Key: App
          Value: mobile-purchases-apple-pubsub

  FeastApplePubsub5xxErrors:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled:
        !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled]
      AlarmActions:
        - Ref: AlarmTopic
      OKActions:
        - Ref: AlarmTopic
      AlarmName: !Sub mobile-purchases-${Stage}-feast-apple-pubsub-check-errors
      AlarmDescription: Two HTTP requests to the iOS Feast pubsub endpoint resulted in 5XX errors.
      Dimensions:
        - Name: ApiName
          Value: !Sub ${App}-${Stage}
        - Name: Method
          Value: POST
        - Name: Resource
          Value: /feast/apple/pubsub
        - Name: Stage
          Value: !Ref Stage
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: 5XXError
      Namespace: AWS/ApiGateway
      Period: 300
      Statistic: Sum
      Threshold: 2
      TreatMissingData: notBreaching
      Tags:
        - Key: App
          Value: mobile-purchases-feast-apple-pubsub

  GooglePubsub5xxErrors:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled:
        !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled]
      AlarmActions:
        - Ref: AlarmTopic
      OKActions:
        - Ref: AlarmTopic
      AlarmName: !Sub mobile-purchases-${Stage}-google-pubsub-check-errors
      AlarmDescription: Two HTTP requests to the Google pubsub endpoint resulted in 5XX errors.
      Dimensions:
        - Name: ApiName
          Value: !Sub ${App}-${Stage}
        - Name: Method
          Value: POST
        - Name: Resource
          Value: /google/pubsub
        - Name: Stage
          Value: !Ref Stage
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: 5XXError
      Namespace: AWS/ApiGateway
      Period: 300
      Statistic: Sum
      Threshold: 2
      TreatMissingData: notBreaching
      Tags:
        - Key: App
          Value: mobile-purchases-google-pubsub

  FeastGooglePubsub5xxErrors:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled:
        !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled]
      AlarmActions:
        - Ref: AlarmTopic
      OKActions:
        - Ref: AlarmTopic
      AlarmName: !Sub mobile-purchases-${Stage}-feast-google-pubsub-check-errors
      AlarmDescription: Two HTTP requests to the Feast Google pubsub endpoint resulted in 5XX errors.
      Dimensions:
        - Name: ApiName
          Value: !Sub ${App}-${Stage}
        - Name: Method
          Value: POST
        - Name: Resource
          Value: /feast/google/pubsub
        - Name: Stage
          Value: !Ref Stage
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: 5XXError
      Namespace: AWS/ApiGateway
      Period: 300
      Statistic: Sum
      Threshold: 2
      TreatMissingData: notBreaching
      Tags:
        - Key: App
          Value: mobile-purchases-feast-google-pubsub

  UserSubscriptionsLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: user-subscriptions.handler
      Runtime: nodejs20.x
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-user-subscriptions/user-subscriptions.zip
      FunctionName: !Sub ${App}-user-subscriptions-${Stage}
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Environment:
        Variables:
          Stage: !Ref Stage
          Stack: !Ref Stack
          App: !Ref App
      Description: Retrieves subscription details for a given user
      MemorySize: 128
      Timeout: 29
      Events:
        InternalAccess:
          Type: Api
          Properties:
            Path: "/user/subscriptions/{userId}"
            Method: GET
            RestApiId: !Ref MobilePuchasesApi
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App

  UpdateAppleSubscriptionsLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${App}-apple-update-subscriptions-${Stage}
      Code:
        S3Bucket: !Ref DeployBucket
        S3Key: !Sub ${Stack}/${Stage}/${App}-apple-update-subscriptions/apple-update-subscriptions.zip
      Environment:
        Variables:
          App: !Sub ${App}
          Stack: !Sub ${Stack}
          Stage: !Sub ${Stage}
          HistoricalQueueUrl: !Sub https://sqs.${AWS::Region}.amazonaws.com/${AWS::AccountId}/${App}-${Stage}-apple-historical-subscriptions
      Description: Consumes subscription data updates from app store from sqs and stores them in dynamo
      Handler: apple-update-subscriptions.handler
      MemorySize: 512
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Timeout: 25
      Runtime: nodejs20.x

  AppleSubscriptionsEventSource:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      FunctionName: !Ref UpdateAppleSubscriptionsLambda
      Enabled: true
      EventSourceArn: !GetAtt AppleSubscriptionsQueue.Arn
      BatchSize: 1

  AppleSubscriptionDlqDepthAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled:
        !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled]
      AlarmDescription: "Ensure that the apple subscription dead letter queue is empty"
      Namespace: "AWS/SQS"
      MetricName: ApproximateNumberOfMessagesVisible
      Dimensions:
        - Name: QueueName
          Value: !GetAtt "AppleSubscriptionsQueueDlq.QueueName"
      Period: 60
      Statistic: Sum
      EvaluationPeriods: 1
      ComparisonOperator: GreaterThanThreshold
      Threshold: 0
      AlarmActions:
        - Ref: AlarmTopic
      OKActions:
        - Ref: AlarmTopic
      TreatMissingData: notBreaching
      Tags:
        - Key: App
          Value: mobile-purchases-apple-update-subscriptions

  GoogleSubscriptionDlqDepthAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled:
        !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled]
      AlarmDescription: "Ensure that the google subscription dead letter queue is empty"
      Namespace: "AWS/SQS"
      MetricName: ApproximateNumberOfMessagesVisible
      Dimensions:
        - Name: QueueName
          Value: !GetAtt "GoogleSubscriptionsQueueDlq.QueueName"
      Period: 60
      Statistic: Sum
      EvaluationPeriods: 1
      ComparisonOperator: GreaterThanThreshold
      Threshold: 0
      AlarmActions:
        - Ref: AlarmTopic
      OKActions:
        - Ref: AlarmTopic
      TreatMissingData: notBreaching
      Tags:
        - Key: App
          Value: mobile-purchases-google-update-subscriptions

  UpdateFeastAppleSubscriptionsLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${App}-feast-apple-update-subscriptions-${Stage}
      Code:
        S3Bucket: !Ref DeployBucket
        S3Key: !Sub ${Stack}/${Stage}/${App}-feast-apple-update-subscriptions/feast-apple-update-subscriptions.zip
      Environment:
        Variables:
          App: !Sub ${App}
          Stack: !Sub ${Stack}
          Stage: !Sub ${Stage}
          HistoricalQueueUrl: !Sub https://sqs.${AWS::Region}.amazonaws.com/${AWS::AccountId}/${App}-${Stage}-apple-historical-subscriptions
      Description: Consumes Feast subscription data updates from app store from sqs and stores them in dynamo
      Handler: feast-apple-update-subscriptions.handler
      MemorySize: 512
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Timeout: 25
      Runtime: nodejs20.x

  UpdateFeastGoogleSubscriptionsLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${App}-feast-google-update-subscriptions-${Stage}
      Code:
        S3Bucket: !Ref DeployBucket
        S3Key: !Sub ${Stack}/${Stage}/${App}-feast-google-update-subscriptions/feast-google-update-subscriptions.zip
      Environment:
        Variables:
          App: !Sub ${App}
          Stack: !Sub ${Stack}
          Stage: !Sub ${Stage}
          HistoricalQueueUrl: !Sub https://sqs.${AWS::Region}.amazonaws.com/${AWS::AccountId}/${App}-${Stage}-google-historical-subscriptions
      Description: Consumes Feast subscription data updates from Google Play store from sqs and stores them in dynamo
      Handler: feast-google-update-subscriptions.handler
      MemorySize: 512
      Role: !GetAtt MobilePurchasesLambdasRole.Arn
      Timeout: 25
      Runtime: nodejs20.x

  FeastAppleSubscriptionsEventSource:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      FunctionName: !Ref UpdateFeastAppleSubscriptionsLambda
      Enabled: true
      EventSourceArn: !GetAtt FeastAppleSubscriptionsQueue.Arn
      BatchSize: 1

  FeastGoogleSubscriptionsEventSource:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      FunctionName: !Ref UpdateFeastGoogleSubscriptionsLambda
      Enabled: true
      EventSourceArn: !GetAtt FeastGoogleSubscriptionsQueue.Arn
      BatchSize: 1

  FeastAppleSubscriptionDlqDepthAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled:
        !FindInMap [StageVariables, !Ref Stage, AlarmActionsEnabled]
      AlarmDescription: "The Feast Apple subscription dead letter queue is over the threshold"
      Namespace: "AWS/SQS"
      MetricName: ApproximateNumberOfMessagesVisible
      Dimensions:
        - Name: QueueName
          Value: !GetAtt "FeastAppleSubscriptionsQueueDlq.QueueName"
      Period: 60
      Statistic: Sum
      EvaluationPeriods: 1
      ComparisonOperator: GreaterThanThreshold
      Threshold: 10
      AlarmActions:
        - Ref: AlarmTopic
      OKActions:
        - Ref: AlarmTopic
      TreatMissingData: notBreaching
      Tags:
        - Key: App
          Value: mobile-purchases-feast-apple-update-subscriptions

  FeastGoogleSubscriptionDlqDepthAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled:
        !FindInMap [ StageVariables, !Ref Stage, AlarmActionsEnabled ]
      AlarmDescription: "Ensure that the Feast Android subscription dead letter queue is empty"
      Namespace: "AWS/SQS"
      MetricName: ApproximateNumberOfMessagesVisible
      Dimensions:
        - Name: QueueName
          Value: !GetAtt "FeastGoogleSubscriptionsQueueDlq.QueueName"
      Period: 60
      Statistic: Sum
      EvaluationPeriods: 1
      ComparisonOperator: GreaterThanThreshold
      Threshold: 0
      AlarmActions:
        - Ref: AlarmTopic
      OKActions:
        - Ref: AlarmTopic
      TreatMissingData: notBreaching
      Tags:
        - Key: App
          Value: mobile-purchases-feast-google-update-subscriptions

  AppleRevalidateSubscriptionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: logs
          PolicyDocument:
            Statement:
              Effect: Allow
              Action:
                - logs:CreateLogGroup
                - logs:CreateLogStream
                - logs:PutLogEvents
                - cloudwatch:putMetricData
              Resource: "*"
        - PolicyName: dynamo
          PolicyDocument:
            Statement:
              Effect: Allow
              Action:
                - "dynamodb:Scan"
              Resource:
                - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions/index/ios-endTimestamp-revalidation-index-with-platform
                - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions
        - PolicyName: sqs
          PolicyDocument:
            Statement:
              Effect: Allow
              Action:
                - "sqs:SendMessage"
              Resource:
                - !Sub arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:${App}-${Stage}-apple-subscriptions-to-fetch
                - !Sub arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:${App}-${Stage}-feast-apple-subscriptions-to-fetch

  DeleteUserSubscriptionLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: delete-user-subscription.handler
      Runtime: nodejs20.x
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-delete-user-subscription/delete-user-subscription.zip
      FunctionName: !Sub ${App}-delete-user-subscription-${Stage}
      Environment:
        Variables:
          App: !Sub ${App}
          Stack: !Sub ${Stack}
          Stage: !Sub ${Stage}
      Description: Delete the link to user IDs when the subscription has reached its end of life
      MemorySize: 512
      Timeout: 60
      Events:
        Schedule:
          Type: DynamoDB
          Properties:
            Stream:
              Fn::ImportValue:
                !Sub ${App}-${Stage}-subscriptions-stream-arn
            StartingPosition: LATEST
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App
      Policies:
        - Statement:
            - Effect: Allow
              Action:
                - logs:CreateLogGroup
                - logs:CreateLogStream
                - logs:PutLogEvents
                - cloudwatch:putMetricData
              Resource: "*"
        - Statement:
            - Effect: Allow
              Action:
                - "dynamodb:GetRecords"
                - "dynamodb:GetShardIterator"
                - "dynamodb:DescribeStream"
                - "dynamodb:ListStreams"
              Resource:
                - Fn::ImportValue:
                    !Sub ${App}-${Stage}-subscriptions-stream-arn
        - Statement:
            - Effect: Allow
              Action:
                - "dynamodb:Query"
                - "dynamodb:DeleteItem"
              Resource:
                - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-user-subscriptions
                - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-user-subscriptions/*
        - Statement:
            - Effect: Allow
              Action:
                - sts:AssumeRole
              Resource:
                !Sub
                - "arn:aws:iam::${MembershipAccountId}:role/membership-${SoftOptInConsentSetterStage}-soft-opt-in-consent-setter-QueueCrossAccountRole"
                - SoftOptInConsentSetterStage: !FindInMap [ StageVariables, !Ref Stage, SoftOptInConsentSetterStage ]
        - Statement:
            - Effect: Allow
              Action:
                - sts:AssumeRole
              Resource: !Sub "arn:aws:iam::${MembershipAccountId}:role/comms-${Stage}-EmailQueueCrossAccountRole"
        - Statement:
            - Effect: Allow
              Action:
                - ssm:GetParametersByPath
              Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${App}/${Stage}/${Stack}/*

  AppleRevalidateReceiptsLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: apple-revalidate-receipts.handler
      Runtime: nodejs20.x
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-apple-revalidate-receipts/apple-revalidate-receipts.zip
      FunctionName: !Sub ${App}-apple-revalidate-receipts-${Stage}
      Role: !GetAtt AppleRevalidateSubscriptionRole.Arn
      Environment:
        Variables:
          App: !Sub ${App}
          Stack: !Sub ${Stack}
          Stage: !Sub ${Stage}
          LiveAppSqsUrl: !Ref AppleSubscriptionsQueue
          FeastAppSqsUrl: !Ref FeastAppleSubscriptionsQueue
      Description: Finds recently expired subscriptions
      MemorySize: 2048
      Timeout: 180
      Events:
        Schedule:
          Type: Schedule
          Properties:
            Schedule: rate(6 hours)
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App

  AcquisitionsDeadLetterQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub ${App}-soft-opt-in-acquisitions-DLQ-${Stage}

  SoftOptInAcquisitionsLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: soft-opt-in-acquisitions.handler
      Runtime: nodejs20.x
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-soft-opt-in-acquisitions/soft-opt-in-acquisitions.zip
      FunctionName: !Sub ${App}-soft-opt-in-acquisitions-${Stage}
      Environment:
        Variables:
          App: !Sub ${App}
          Stack: !Sub ${Stack}
          Stage: !Sub ${Stage}
          DLQUrl: !Ref AcquisitionsDeadLetterQueue
      Description: Trigger setting soft-opt-in consents and sending emails based on Dynamo events
      MemorySize: 512
      Timeout: 60
      Events:
        Schedule:
          Type: DynamoDB
          Properties:
            Stream:
              Fn::ImportValue: !Sub ${App}-${Stage}-user-subscriptions-stream-arn
            StartingPosition: LATEST
            MaximumRecordAgeInSeconds: 28800
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App
      Policies:
        - Statement:
            - Effect: Allow
              Action:
                - sts:AssumeRole
              Resource:
                !Sub
                - "arn:aws:iam::${MembershipAccountId}:role/membership-${SoftOptInConsentSetterStage}-soft-opt-in-consent-setter-QueueCrossAccountRole"
                - SoftOptInConsentSetterStage: !FindInMap [ StageVariables, !Ref Stage, SoftOptInConsentSetterStage ]
        - Statement:
            - Effect: Allow
              Action:
                - sts:AssumeRole
              Resource: !Sub "arn:aws:iam::${MembershipAccountId}:role/comms-${Stage}-EmailQueueCrossAccountRole"
        - Statement:
            - Effect: Allow
              Action:
                - "dynamodb:Query"
                - "dynamodb:GetItem"
              Resource:
                - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions
                - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions/*
        - Statement:
            - Effect: Allow
              Action:
                - ssm:GetParametersByPath
              Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${App}/${Stage}/${Stack}/*
        - Statement:
            - Effect: Allow
              Action:
                - logs:CreateLogGroup
                - logs:CreateLogStream
                - logs:PutLogEvents
                - cloudwatch:putMetricData
              Resource: "*"
        - Statement:
            - Effect: Allow
              Action:
                - "dynamodb:GetRecords"
                - "dynamodb:GetShardIterator"
                - "dynamodb:DescribeStream"
                - "dynamodb:ListStreams"
              Resource:
                - Fn::ImportValue: !Sub ${App}-${Stage}-user-subscriptions-stream-arn
        - Statement:
            - Effect: Allow
              Action:
                - sqs:DeleteMessage
                - sqs:GetQueueAttributes
                - sqs:ReceiveMessage
              Resource: "*"
        - Statement:
            - Effect: Allow
              Action:
                - sqs:SendMessage
              Resource: !GetAtt AcquisitionsDeadLetterQueue.Arn

  AcquisitionsDLQProcessorLambda:
    Type: AWS::Serverless::Function
    Properties:
      Handler: soft-opt-in-acquisitions-dlq-processor.handler
      Runtime: nodejs20.x
      CodeUri:
        Bucket: !Ref DeployBucket
        Key: !Sub ${Stack}/${Stage}/${App}-soft-opt-in-acquisitions-dlq-processor/soft-opt-in-acquisitions-dlq-processor.zip
      FunctionName: !Sub ${App}-soft-opt-in-acquisitions-dlq-processor-${Stage}
      Environment:
        Variables:
          App: !Sub ${App}
          Stack: !Sub ${Stack}
          Stage: !Sub ${Stage}
          DLQUrl: !Ref AcquisitionsDeadLetterQueue
      Description: Process DLQ messages
      MemorySize: 512
      Timeout: 60
      Tags:
        Stage: !Ref Stage
        Stack: !Ref Stack
        App: !Ref App
      Events:
        ScheduledRun:
          Type: Schedule
          Properties:
            Schedule: !FindInMap [ StageVariables, !Ref Stage, Schedule]
            Description: Runs AcquisitionsDLQProcessorLambda
            Enabled: True
      Policies:
        - Statement:
            - Effect: Allow
              Action:
                - sts:AssumeRole
              Resource:
                !Sub
                - "arn:aws:iam::${MembershipAccountId}:role/membership-${SoftOptInConsentSetterStage}-soft-opt-in-consent-setter-QueueCrossAccountRole"
                - SoftOptInConsentSetterStage: !FindInMap [ StageVariables, !Ref Stage, SoftOptInConsentSetterStage ]
        - Statement:
            - Effect: Allow
              Action:
                - sts:AssumeRole
              Resource: !Sub "arn:aws:iam::${MembershipAccountId}:role/comms-${Stage}-EmailQueueCrossAccountRole"
        - Statement:
            - Effect: Allow
              Action:
                - "dynamodb:Query"
                - "dynamodb:GetItem"
              Resource:
                - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions
                - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${App}-${Stage}-subscriptions/*
        - Statement:
            - Effect: Allow
              Action:
                - ssm:GetParametersByPath
              Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${App}/${Stage}/${Stack}/*
        - Statement:
            - Effect: Allow
              Action:
                - logs:CreateLogGroup
                - logs:CreateLogStream
                - logs:PutLogEvents
                - cloudwatch:putMetricData
              Resource: "*"
        - Statement:
            - Effect: Allow
              Action:
                - sqs:DeleteMessage
                - sqs:GetQueueAttributes
                - sqs:ReceiveMessage
              Resource: "*"

  failedSettingCancellationSoftOptIns:
    Condition: IsProd
    Type: AWS::CloudWatch::Alarm
    DependsOn:
      - DeleteUserSubscriptionLambda
    Properties:
      AlarmActions:
        - Ref: AlarmTopic
      AlarmName: !Sub ${App}-delete-user-subscription-${Stage} failed to set soft opt-ins for a user
      AlarmDescription: >
        An error occurred in the DeleteUserSubscriptionLambda and failed to set soft opt-ins for a user
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Dimensions:
        - Name: Stage
          Value: !Sub ${Stage}
      EvaluationPeriods: 1
      MetricName: failed_to_send_cancellation_message
      Namespace: AWS/Lambda
      Period: 3600
      Statistic: Sum
      Threshold: 1
      TreatMissingData: notBreaching
      Tags:
        - Key: App
          Value: mobile-purchases-delete-user-subscription

  acquisitionsLambdaExceptionsAlarm:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    DependsOn:
      - SoftOptInAcquisitionsLambda
    Properties:
      AlarmActions:
        - Ref: AlarmTopic
      AlarmName: !Sub ${App}-soft-opt-in-acquisitions-${Stage} threw an unhandled exception and failed to set soft opt-ins for a user
      AlarmDescription: >
        An error occurred in the SoftOptInAcquisitionsLambda and failed to set soft opt-ins for a user
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Dimensions:
        - Name: FunctionName
          Value: !Ref SoftOptInAcquisitionsLambda
      EvaluationPeriods: 1
      MetricName: Errors
      Namespace: AWS/Lambda
      Period: 3600
      Statistic: Sum
      Threshold: 1
      TreatMissingData: notBreaching
      Tags:
        - Key: App
          Value: mobile-purchases-soft-opt-in-acquisitions

  acquisitionsDlqProcessorExceptionsAlarm:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    DependsOn:
      - AcquisitionsDLQProcessorLambda
    Properties:
      AlarmActions:
        - Ref: AlarmTopic
      AlarmName: !Sub ${App}-soft-opt-ins-acquisitions-dlq-processor-${Stage} threw an unhandled exception
      AlarmDescription: >
        An error occurred in the AcquisitionsDLQProcessorLambda
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Dimensions:
        - Name: FunctionName
          Value: !Ref AcquisitionsDLQProcessorLambda
      EvaluationPeriods: 1
      MetricName: Errors
      Namespace: AWS/Lambda
      Period: 3600
      Statistic: Sum
      Threshold: 1
      TreatMissingData: notBreaching
      Tags:
        - Key: App
          Value: mobile-purchases-soft-opt-in-acquisitions-dlq-processor

  MembershipCloudwatchRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !Sub arn:aws:iam::${MembershipAccountId}:root
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: cloudwatch-list-tags
          PolicyDocument:
            Statement:
              Effect: Allow
              Action:
                - cloudwatch:ListTagsForResource
              Resource: "*"
