Skip to content

Commit

Permalink
Merge branch 'main' into tf-lp-config
Browse files Browse the repository at this point in the history
  • Loading branch information
tomrf1 committed Feb 3, 2025
2 parents 8fcb4d9 + f2bbb0a commit 5a74315
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 21 deletions.
90 changes: 74 additions & 16 deletions cdk/lib/support-workers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,20 @@ import {
import { LambdaInvoke } from "aws-cdk-lib/aws-stepfunctions-tasks";

type PaymentProvider = "Stripe" | "DirectDebit" | "PayPal";
type ProductType = "Contribution" | "Paper" | "GuardianWeekly" | "SupporterPlus" | "TierThree";
type ProductType =
| "Contribution"
| "Paper"
| "GuardianWeekly"
| "SupporterPlus"
| "TierThree";

interface SupportWorkersProps extends GuStackProps {
promotionsDynamoTables: string[];
s3Files: string[];
supporterProductDataTables: string[];
eventBusArns: string[];
}

export class SupportWorkers extends GuStack {
constructor(scope: App, id: string, props: SupportWorkersProps) {
super(scope, id, props);
Expand Down Expand Up @@ -321,7 +327,11 @@ export class SupportWorkers extends GuStack {
actionsEnabled: isProd,
snsTopicName: `alarms-handler-topic-${this.stage}`,
alarmName: `support-workers ${this.stage} No successful recurring paypal contributions recently.`,
metric: this.buildPaymentSuccessMetric("PayPal", "Contribution", Duration.seconds(3600)),
metric: this.buildPaymentSuccessMetric(
"PayPal",
"Contribution",
Duration.seconds(3600)
),
comparisonOperator: ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
evaluationPeriods: 4,
treatMissingData: TreatMissingData.BREACHING,
Expand All @@ -333,7 +343,11 @@ export class SupportWorkers extends GuStack {
actionsEnabled: isProd,
snsTopicName: `alarms-handler-topic-${this.stage}`,
alarmName: `support-workers ${this.stage} No successful recurring stripe contributions recently.`,
metric: this.buildPaymentSuccessMetric("Stripe", "Contribution", Duration.seconds(3600)),
metric: this.buildPaymentSuccessMetric(
"Stripe",
"Contribution",
Duration.seconds(3600)
),
comparisonOperator: ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
evaluationPeriods: 3,
treatMissingData: TreatMissingData.BREACHING,
Expand All @@ -345,7 +359,11 @@ export class SupportWorkers extends GuStack {
actionsEnabled: isProd,
snsTopicName: `alarms-handler-topic-${this.stage}`,
alarmName: `support-workers ${this.stage} No successful recurring gocardless contributions recently.`,
metric: this.buildPaymentSuccessMetric("DirectDebit", "Contribution", Duration.seconds(3600)),
metric: this.buildPaymentSuccessMetric(
"DirectDebit",
"Contribution",
Duration.seconds(3600)
),
comparisonOperator: ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
evaluationPeriods: 18,
treatMissingData: TreatMissingData.BREACHING,
Expand All @@ -357,7 +375,11 @@ export class SupportWorkers extends GuStack {
actionsEnabled: isProd,
snsTopicName: `alarms-handler-topic-${this.stage}`,
alarmName: `support-workers ${this.stage} No successful recurring paypal supporter plus contributions recently.`,
metric: this.buildPaymentSuccessMetric("PayPal", "SupporterPlus", Duration.seconds(3600)),
metric: this.buildPaymentSuccessMetric(
"PayPal",
"SupporterPlus",
Duration.seconds(3600)
),
comparisonOperator: ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
evaluationPeriods: 6,
treatMissingData: TreatMissingData.BREACHING,
Expand All @@ -369,7 +391,11 @@ export class SupportWorkers extends GuStack {
actionsEnabled: isProd,
snsTopicName: `alarms-handler-topic-${this.stage}`,
alarmName: `support-workers ${this.stage} No successful recurring stripe supporter plus contributions recently.`,
metric: this.buildPaymentSuccessMetric("Stripe", "SupporterPlus", Duration.seconds(3600)),
metric: this.buildPaymentSuccessMetric(
"Stripe",
"SupporterPlus",
Duration.seconds(3600)
),
comparisonOperator: ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
evaluationPeriods: 3,
treatMissingData: TreatMissingData.BREACHING,
Expand All @@ -381,7 +407,11 @@ export class SupportWorkers extends GuStack {
actionsEnabled: isProd,
snsTopicName: `alarms-handler-topic-${this.stage}`,
alarmName: `support-workers ${this.stage} No successful recurring gocardless supporter plus contributions recently.`,
metric: this.buildPaymentSuccessMetric("DirectDebit", "SupporterPlus", Duration.seconds(3600)),
metric: this.buildPaymentSuccessMetric(
"DirectDebit",
"SupporterPlus",
Duration.seconds(3600)
),
comparisonOperator: ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
evaluationPeriods: 18,
treatMissingData: TreatMissingData.BREACHING,
Expand All @@ -399,9 +429,21 @@ export class SupportWorkers extends GuStack {
expression: "SUM([FILL(m1,0),FILL(m2,0),FILL(m3,0)])",
label: "AllPaperConversions",
usingMetrics: {
m1: this.buildPaymentSuccessMetric("Stripe", "Paper", Duration.seconds(300)),
m2: this.buildPaymentSuccessMetric("DirectDebit", "Paper", Duration.seconds(300)),
m3: this.buildPaymentSuccessMetric("PayPal", "Paper", Duration.seconds(300)),
m1: this.buildPaymentSuccessMetric(
"Stripe",
"Paper",
Duration.seconds(300)
),
m2: this.buildPaymentSuccessMetric(
"DirectDebit",
"Paper",
Duration.seconds(300)
),
m3: this.buildPaymentSuccessMetric(
"PayPal",
"Paper",
Duration.seconds(300)
),
},
}),
comparisonOperator: ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
Expand All @@ -419,9 +461,21 @@ export class SupportWorkers extends GuStack {
label: "AllWeeklyConversions",
expression: "SUM([FILL(m1,0),FILL(m2,0),FILL(m3,0)])",
usingMetrics: {
m1: this.buildPaymentSuccessMetric("Stripe", "GuardianWeekly", Duration.seconds(300)),
m2: this.buildPaymentSuccessMetric("DirectDebit", "GuardianWeekly", Duration.seconds(300)),
m3: this.buildPaymentSuccessMetric("PayPal", "GuardianWeekly", Duration.seconds(300)),
m1: this.buildPaymentSuccessMetric(
"Stripe",
"GuardianWeekly",
Duration.seconds(300)
),
m2: this.buildPaymentSuccessMetric(
"DirectDebit",
"GuardianWeekly",
Duration.seconds(300)
),
m3: this.buildPaymentSuccessMetric(
"PayPal",
"GuardianWeekly",
Duration.seconds(300)
),
},
}),
comparisonOperator: ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
Expand Down Expand Up @@ -470,7 +524,11 @@ export class SupportWorkers extends GuStack {
}).node.addDependency(stateMachine);
}

buildPaymentSuccessMetric = (paymentProvider: PaymentProvider, productType: ProductType, period: Duration) => {
buildPaymentSuccessMetric = (
paymentProvider: PaymentProvider,
productType: ProductType,
period: Duration
) => {
return new Metric({
metricName: "PaymentSuccess",
namespace: "support-frontend",
Expand All @@ -481,6 +539,6 @@ export class SupportWorkers extends GuStack {
},
statistic: "Sum",
period: period,
})
}
});
};
}
3 changes: 2 additions & 1 deletion support-e2e/tests/test/checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ export const testCheckout = (testDetails: TestDetails) => {
context,
baseURL,
}) => {
const url = `/${internationalisationId.toLowerCase()}/checkout?product=${product}&ratePlan=${ratePlan}`;
// Temporary opt out of this test
const url = `/${internationalisationId.toLowerCase()}/checkout?product=${product}&ratePlan=${ratePlan}#ab-confirmEmail=control`;
const page = await context.newPage();
await setupPage(page, context, baseURL, url);

Expand Down
21 changes: 21 additions & 0 deletions support-frontend/assets/helpers/abTests/abtestDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,25 @@ export const tests: Tests = {
targetPage: pageUrlRegexes.contributions.allLandingPagesAndThankyouPages,
excludeContributionsOnlyCountries: true,
},
confirmEmail: {
variants: [
{
id: 'control',
},
{
id: 'variant',
},
],
audiences: {
ALL: {
offset: 0,
size: 1,
},
},
isActive: true,
referrerControlled: false, // ab-test name not needed to be in paramURL
seed: 5,
targetPage: pageUrlRegexes.contributions.genericCheckoutOnly,
excludeContributionsOnlyCountries: false,
},
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TextInput } from '@guardian/source/react-components';
import escapeStringRegexp from 'escape-string-regexp';
import { useState } from 'react';
import {
doesNotContainExtendedEmojiOrLeadingSpace,
Expand All @@ -14,6 +15,9 @@ type PersonalDetailsFieldsProps = {
email: string;
setEmail: (value: string) => void;
isEmailAddressReadOnly: boolean;
requireConfirmedEmail: boolean;
confirmedEmail: string;
setConfirmedEmail: (value: string) => void;
};

export function PersonalDetailsFields({
Expand All @@ -25,10 +29,14 @@ export function PersonalDetailsFields({
email,
setEmail,
isEmailAddressReadOnly,
requireConfirmedEmail,
confirmedEmail,
setConfirmedEmail,
}: PersonalDetailsFieldsProps) {
const [firstNameError, setFirstNameError] = useState<string>();
const [lastNameError, setLastNameError] = useState<string>();
const [emailError, setEmailError] = useState<string>();
const [confirmedEmailError, setConfirmedEmailError] = useState<string>();

return (
<>
Expand Down Expand Up @@ -66,6 +74,44 @@ export function PersonalDetailsFields({
}}
/>
</div>
{requireConfirmedEmail && !isEmailAddressReadOnly && (
<div>
<TextInput
id="confirm-email"
data-qm-masking="blocklist"
label="Confirm email address"
value={confirmedEmail}
type="email"
autoComplete="email"
onChange={(event) => {
setConfirmedEmail(event.currentTarget.value);
}}
onBlur={(event) => {
event.target.checkValidity();
}}
name="confirm-email"
required
maxLength={80}
error={confirmedEmailError}
pattern={escapeStringRegexp(email)}
onInvalid={(event) => {
preventDefaultValidityMessage(event.currentTarget);
const validityState = event.currentTarget.validity;
if (validityState.valid) {
setConfirmedEmailError(undefined);
} else {
if (validityState.valueMissing) {
setConfirmedEmailError('Please confirm your email address.');
} else if (validityState.patternMismatch) {
setConfirmedEmailError('The email addresses do not match.');
} else {
setConfirmedEmailError('Please enter a valid email address.');
}
}
}}
/>
</div>
)}
{children}
<div>
<TextInput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ export function CheckoutComponent({
abParticipations.newspaperArchiveBenefit ?? '',
);

const inConfirmEmailVariant = abParticipations.confirmEmail === 'variant';

const productDescription = showNewspaperArchiveBenefit
? productCatalogDescriptionNewBenefits(countryGroupId)[productKey]
: productCatalogDescription[productKey];
Expand Down Expand Up @@ -381,6 +383,7 @@ export function CheckoutComponent({
const [firstName, setFirstName] = useState(user?.firstName ?? '');
const [lastName, setLastName] = useState(user?.lastName ?? '');
const [email, setEmail] = useState(user?.email ?? '');
const [confirmedEmail, setConfirmedEmail] = useState('');

/** Delivery and billing addresses */
const [deliveryPostcode, setDeliveryPostcode] = useState('');
Expand Down Expand Up @@ -438,6 +441,7 @@ export function CheckoutComponent({

const formOnSubmit = async (formData: FormData) => {
setIsProcessingPayment(true);

/**
* The validation for this is currently happening on the client side form validation
* So we'll assume strings are not null.
Expand Down Expand Up @@ -571,10 +575,6 @@ export function CheckoutComponent({
labels: ['generic-checkout'],
};

if (stripeExpressCheckoutPaymentType === 'link') {
referrerAcquisitionData.labels.push('express-checkout-link');
}

if (paymentMethod && paymentFields) {
/** TODO
* - add debugInfo
Expand Down Expand Up @@ -879,6 +879,8 @@ export function CheckoutComponent({

event.billingDetails?.email &&
setEmail(event.billingDetails.email);
event.billingDetails?.email &&
setConfirmedEmail(event.billingDetails.email);

setPaymentMethod('StripeExpressCheckoutElement');
setStripeExpressCheckoutPaymentType(
Expand Down Expand Up @@ -942,6 +944,11 @@ export function CheckoutComponent({
setLastName={(lastName) => setLastName(lastName)}
email={email}
setEmail={(email) => setEmail(email)}
requireConfirmedEmail={inConfirmEmailVariant}
confirmedEmail={confirmedEmail}
setConfirmedEmail={(confirmedEmail) =>
setConfirmedEmail(confirmedEmail)
}
>
<Signout isSignedIn={isSignedIn} />
</PersonalDetailsFields>
Expand Down
1 change: 1 addition & 0 deletions support-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"@types/uuid": "^9.0.2",
"classnames": "^2.5.1",
"dompurify": "^3.2.0",
"escape-string-regexp": "5.0.0",
"framer-motion": "^1.11.1",
"lodash.debounce": "^4.0.6",
"mockdate": "^3.0.5",
Expand Down
5 changes: 5 additions & 0 deletions support-frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5700,6 +5700,11 @@ escape-html@~1.0.3:
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=

[email protected]:
version "5.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==

escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
Expand Down

0 comments on commit 5a74315

Please sign in to comment.