Transform: AWS::Serverless-2016-10-31
Description: Source code in https://github.com/guardian/payment-failure-comms

Parameters:
  Stage:
    Description: Stage name
    Type: String
    AllowedValues:
      - PROD
      - CODE
      - DEV
    Default: DEV
  AppName:
    Type: String
    Default: payment-failure-comms

Conditions:
  IsProd: !Equals [ !Ref Stage, PROD ]

Mappings:
  StageMap:
    PROD:
      Schedule: 'rate(20 minutes)'
      SalesforceStage: PROD
      SalesforceUserName: pfCommsAPIUser
      SalesforceAppName: PfComms
      IdapiInstanceUrl: idapi.theguardian.com
      IdapiStage: PROD
      BrazeInstanceUrl: rest.fra-01.braze.eu
      BrazeAppGroup: LIVE
    CODE:
      Schedule: 'rate(365 days)'
      SalesforceStage: UAT
      SalesforceUserName: pfCommsAPIUser
      SalesforceAppName: AwsConnectorSandbox
      IdapiInstanceUrl: idapi.code.dev-theguardian.com
      IdapiStage: CODE
      BrazeInstanceUrl: rest.fra-01.braze.eu
      BrazeAppGroup: DEV
    DEV:
      Schedule: 'rate(365 days)'
      SalesforceStage: DEV
      SalesforceUserName: pfCommsAPIUser
      SalesforceAppName: AwsConnectorSandbox
      IdapiInstanceUrl: idapi.code.dev-theguardian.com
      IdapiStage: CODE
      BrazeInstanceUrl: rest.fra-01.braze.eu
      BrazeAppGroup: DEV

Resources:

  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${AppName}-${Stage}
      RetentionInDays: 90

  PaymentFailureCommsLambda:
    Type: AWS::Serverless::Function
    DependsOn: LogGroup
    Properties:
      FunctionName: !Sub ${AppName}-${Stage}
      Handler: payment_failure_comms.Handler::handleRequest
      Runtime: java8.al2
      CodeUri:
        Bucket: membership-dist
        Key: !Sub membership/${Stage}/${AppName}/${AppName}.jar
      Timeout: 120
      MemorySize: 256
      Environment:
        Variables:
          stage: !Ref Stage
          salesforceApiVersion: v46.0
          salesforceInstanceUrl:
            !Sub
            - '{{resolve:secretsmanager:${SalesforceStage}/Salesforce/ConnectedApp/${SalesforceAppName}:SecretString:authUrl}}'
            - SalesforceStage: !FindInMap [ StageMap, !Ref Stage, SalesforceStage ]
              SalesforceAppName: !FindInMap [ StageMap, !Ref Stage, SalesforceAppName ]
          salesforceClientId:
            !Sub
            - '{{resolve:secretsmanager:${SalesforceStage}/Salesforce/ConnectedApp/${SalesforceAppName}:SecretString:clientId}}'
            - SalesforceStage: !FindInMap [ StageMap, !Ref Stage, SalesforceStage ]
              SalesforceAppName: !FindInMap [ StageMap, !Ref Stage, SalesforceAppName ]
          salesforceClientSecret:
            !Sub
            - '{{resolve:secretsmanager:${SalesforceStage}/Salesforce/ConnectedApp/${SalesforceAppName}:SecretString:clientSecret}}'
            - SalesforceStage: !FindInMap [ StageMap, !Ref Stage, SalesforceStage ]
              SalesforceAppName: !FindInMap [ StageMap, !Ref Stage, SalesforceAppName ]
          salesforceUserName:
            !Sub
            - '{{resolve:secretsmanager:${SalesforceStage}/Salesforce/User/${SalesforceUserName}:SecretString:userName}}'
            - SalesforceStage: !FindInMap [ StageMap, !Ref Stage, SalesforceStage ]
              SalesforceUserName: !FindInMap [ StageMap, !Ref Stage, SalesforceUserName ]
          salesforcePassword:
            !Sub
            - '{{resolve:secretsmanager:${SalesforceStage}/Salesforce/User/${SalesforceUserName}:SecretString:password}}'
            - SalesforceStage: !FindInMap [ StageMap, !Ref Stage, SalesforceStage ]
              SalesforceUserName: !FindInMap [ StageMap, !Ref Stage, SalesforceUserName ]
          salesforceToken:
            !Sub
            - '{{resolve:secretsmanager:${SalesforceStage}/Salesforce/User/${SalesforceUserName}:SecretString:token}}'
            - SalesforceStage: !FindInMap [ StageMap, !Ref Stage, SalesforceStage ]
              SalesforceUserName: !FindInMap [ StageMap, !Ref Stage, SalesforceUserName ]
          idapiInstanceUrl: !FindInMap [ StageMap, !Ref Stage, IdapiInstanceUrl ]
          idapiBearerToken: !Sub
            - '{{resolve:secretsmanager:${IdapiStage}/Identity/${AppName}:SecretString:bearerToken}}'
            - IdapiStage: !FindInMap [ StageMap, !Ref Stage, IdapiStage ]
          brazeInstanceUrl: !FindInMap [ StageMap, !Ref Stage, BrazeInstanceUrl ]
          brazeApiKey:
            !Sub
            - '{{resolve:secretsmanager:${BrazeAppGroup}/Braze/${AppName}:SecretString:apiKey}}'
            - BrazeAppGroup: !FindInMap [ StageMap, !Ref Stage, BrazeAppGroup ]
          appIdForBraze:
            !Sub
            - '{{resolve:secretsmanager:${BrazeAppGroup}/Braze/${AppName}:SecretString:appId}}'
            - BrazeAppGroup: !FindInMap [ StageMap, !Ref Stage, BrazeAppGroup ]
      Events:
        ScheduledRun:
          Type: Schedule
          Properties:
            Description: Send payment failure communications
            Schedule: !FindInMap [ StageMap, !Ref Stage, Schedule ]
            RetryPolicy:
              # As process runs every 20 mins anyway, there's no point in retrying on failure
              MaximumRetryAttempts: 0
            Enabled: True
      Policies:
        - Statement:
            - Effect: Allow
              Action: cloudwatch:PutMetricData
              Resource: "*"
      EventInvokeConfig:
        MaximumRetryAttempts: 0

  FailureAlarm:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    DependsOn: PaymentFailureCommsLambda
    Properties:
      AlarmName: "payment-failure-comms record syncing job is consistently failing to complete"
      AlarmDescription: >
        IMPACT: Until the job is repaired, new payment-failure events will not be communicated to customers.
        For resolution steps, see https://github.com/guardian/payment-failure-comms/blob/main/README.md#alarms.
      AlarmActions:
        - !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:alarms-handler-topic-PROD
      Metrics:
        - Id: errors
          Expression: "FILL(m1,0)"
          Label: ErrorsCount
        - Id: m1
          ReturnData: false
          MetricStat:
            Metric:
              MetricName: Errors
              Namespace: AWS/Lambda
              Dimensions:
                - Name: FunctionName
                  Value: !Ref PaymentFailureCommsLambda
            Stat: Sum
            Period: 60
            Unit: Count
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Threshold: 1
      EvaluationPeriods: 90
      DatapointsToAlarm: 5
      TreatMissingData: ignore

  FailureLimitReachedAlarm:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    DependsOn: PaymentFailureCommsLambda
    Properties:
      AlarmName: "Action required - payment-failure-comms: failure limit reached for some records"
      AlarmDescription: >
        IMPACT: At least one payment-failure event will not be communicated to the customer.
        For resolution steps, see https://github.com/guardian/payment-failure-comms/blob/main/README.md#alarms.
      AlarmActions:
        - !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:alarms-handler-topic-PROD
      MetricName: failure-limit-reached
      Namespace: payment-failure-comms
      Dimensions:
        - Name: Stage
          Value: !Ref Stage
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Threshold: 5
      Statistic: Sum
      Period: 3600
      EvaluationPeriods: 1
      TreatMissingData: notBreaching
