Skip to content

Commit 75cba52

Browse files
author
Olivier Andrade
committed
refactor(auto-cancel): migrate to SrCDK with ApiGatewayToSqs, removing intermediate QueueWriter lambda
1 parent b7c92b8 commit 75cba52

File tree

7 files changed

+210
-435
lines changed

7 files changed

+210
-435
lines changed

.github/workflows/ci-scala.yml

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ jobs:
103103
- sf-emails-to-s3-exporter
104104
- sf-gocardless-sync
105105
- sf-move-subscriptions-api
106-
- zuora-callout-apis
107106
- zuora-datalake-export
108107
- zuora-rer
109108
- zuora-sar
@@ -137,3 +136,58 @@ jobs:
137136
LAST_TEAMCITY_BUILD=6410
138137
export GITHUB_RUN_NUMBER=$(( $GITHUB_RUN_NUMBER + $LAST_TEAMCITY_BUILD ))
139138
sbt "project ${{ matrix.subproject }}" riffRaffUpload
139+
140+
zuora-callout-apis:
141+
runs-on: ubuntu-latest
142+
permissions:
143+
id-token: write
144+
contents: read
145+
pull-requests: write
146+
steps:
147+
- name: Checkout
148+
uses: actions/checkout@v6
149+
150+
- name: Configure AWS credentials
151+
uses: aws-actions/configure-aws-credentials@v5
152+
with:
153+
role-to-assume: ${{ secrets.GU_RIFF_RAFF_ROLE_ARN }}
154+
aws-region: eu-west-1
155+
156+
- name: Setup Java
157+
uses: actions/setup-java@v5
158+
with:
159+
distribution: 'corretto'
160+
java-version: '21'
161+
cache: 'sbt'
162+
163+
- name: Setup SBT
164+
uses: sbt/setup-sbt@v1
165+
166+
- name: Run tests and build subproject JAR
167+
run: sbt "project zuora-callout-apis" test assembly
168+
169+
- run: npm install --global [email protected]
170+
- run: corepack enable
171+
shell: bash
172+
- uses: actions/setup-node@v6
173+
with:
174+
node-version-file: '.nvmrc'
175+
cache: 'pnpm'
176+
- run: pnpm install
177+
- run: pnpm --filter cdk package
178+
179+
- name: Upload to Riff-Raff
180+
uses: guardian/actions-riff-raff@v4
181+
with:
182+
githubToken: ${{ secrets.GITHUB_TOKEN }}
183+
roleArn: ${{ secrets.GU_RIFF_RAFF_ROLE_ARN }}
184+
projectName: support-service-lambdas::zuora-callout-apis
185+
buildNumberOffset: 7000
186+
configPath: ./handlers/zuora-callout-apis/riff-raff.yaml
187+
commentingEnabled: 'false'
188+
contentDirectories: |
189+
zuora-auto-cancel-cloudformation:
190+
- ./cdk/cdk.out/zuora-auto-cancel-CODE.template.json
191+
- ./cdk/cdk.out/zuora-auto-cancel-PROD.template.json
192+
zuora-callout-apis:
193+
- ./handlers/zuora-callout-apis/target/scala-2.13/zuora-callout-apis.jar

cdk/bin/cdk.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { TicketTailorWebhook } from '../lib/ticket-tailor-webhook';
3838
import { UpdateSupporterPlusAmount } from '../lib/update-supporter-plus-amount';
3939
import { UserBenefits } from '../lib/user-benefits';
4040
import { WriteOffUnpaidInvoices } from '../lib/write-off-unpaid-invoices';
41+
import { ZuoraAutoCancel } from '../lib/zuora-auto-cancel';
4142
import { ZuoraSalesforceLinkRemover } from '../lib/zuora-salesforce-link-remover';
4243

4344
const app = new App();
@@ -148,6 +149,7 @@ const stacks: Array<new (app: App, stage: SrStageNames) => unknown> = [
148149
TicketTailorWebhook,
149150
MobilePurchasesToSupporterProductData,
150151
StripeDisputes,
152+
ZuoraAutoCancel,
151153
];
152154

153155
// generate all stacks for all stages

cdk/lib/zuora-auto-cancel.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import type { App } from 'aws-cdk-lib';
2+
import { Duration } from 'aws-cdk-lib';
3+
import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam';
4+
import { Architecture, CfnEventSourceMapping, Runtime } from 'aws-cdk-lib/aws-lambda';
5+
import { ApiGatewayToSqs } from './cdk/ApiGatewayToSqs';
6+
import { SrSqsLambda } from './cdk/SrSqsLambda';
7+
import type { SrStageNames } from './cdk/SrStack';
8+
import { SrStack } from './cdk/SrStack';
9+
10+
export class ZuoraAutoCancel extends SrStack {
11+
constructor(scope: App, stage: SrStageNames) {
12+
super(scope, { app: 'zuora-auto-cancel', stack: 'membership', stage });
13+
14+
const errorImpact =
15+
'Zuora auto-cancellations are not being processed. Subscriptions with failed payments may not be cancelled.';
16+
17+
// SQS-triggered Lambda using SrCDK with Java overrides
18+
const lambda = new SrSqsLambda(this, 'Lambda', {
19+
monitoring: { errorImpact },
20+
maxReceiveCount: 3,
21+
visibilityTimeout: Duration.minutes(6), // Must be > Lambda timeout
22+
lambdaOverrides: {
23+
description:
24+
'Processes auto-cancellation requests from SQS queue (rate-limited)',
25+
fileName: 'zuora-callout-apis.jar',
26+
handler: 'com.gu.autoCancel.AutoCancelSqsHandler::handleRequest',
27+
runtime: Runtime.JAVA_21,
28+
architecture: Architecture.ARM_64,
29+
memorySize: 1536,
30+
timeout: Duration.minutes(5),
31+
environment: {
32+
Stage: this.stage,
33+
},
34+
},
35+
});
36+
37+
// Add maxConcurrency to the event source mapping to limit concurrent Zuora API calls
38+
// SrSqsLambda creates an event source with batchSize: 1, we need to add ScalingConfig
39+
lambda.node.findAll().forEach((child) => {
40+
const cfnResource = child.node.defaultChild;
41+
if (cfnResource instanceof CfnEventSourceMapping) {
42+
cfnResource.scalingConfig = { maximumConcurrency: 5 };
43+
}
44+
});
45+
46+
// IAM Policies
47+
lambda.addPolicies(
48+
new Policy(this, 'ReadPrivateCredentials', {
49+
statements: [
50+
new PolicyStatement({
51+
actions: ['s3:GetObject'],
52+
resources: [
53+
`arn:aws:s3:::gu-reader-revenue-private/membership/support-service-lambdas/${this.stage}/zuoraRest-${this.stage}.*.json`,
54+
`arn:aws:s3:::gu-reader-revenue-private/membership/support-service-lambdas/${this.stage}/trustedApi-${this.stage}.*.json`,
55+
`arn:aws:s3:::gu-reader-revenue-private/membership/support-service-lambdas/${this.stage}/exactTarget-${this.stage}.*.json`,
56+
`arn:aws:s3:::gu-reader-revenue-private/membership/support-service-lambdas/${this.stage}/stripe-${this.stage}.*.json`,
57+
],
58+
}),
59+
],
60+
}),
61+
new Policy(this, 'SQSSendToEmailQueue', {
62+
statements: [
63+
new PolicyStatement({
64+
actions: ['sqs:SendMessage', 'sqs:GetQueueUrl'],
65+
resources: [
66+
`arn:aws:sqs:${this.region}:${this.account}:comms-${this.stage}-EmailQueue`,
67+
],
68+
}),
69+
],
70+
}),
71+
);
72+
73+
// API Gateway -> SQS integration (replaces the AutoCancelQueueWriter lambda)
74+
new ApiGatewayToSqs(this, 'ApiGatewayToSqs', {
75+
queue: lambda.inputQueue,
76+
monitoring: { errorImpact },
77+
});
78+
}
79+
}

0 commit comments

Comments
 (0)