From 8c42ac5f95e8e29700655efa880c3994ae3ad7e6 Mon Sep 17 00:00:00 2001
From: Joshua Hawxwell <m@hawx.me>
Date: Thu, 16 Nov 2023 13:30:27 +0000
Subject: [PATCH] MLPAB-1365 Prevent re-checking LPA when no changes have been
 made (#850)

---
 cypress/e2e/donor/check-your-lpa.cy.js        |  15 ++
 go.mod                                        |   1 +
 go.sum                                        |   2 +
 internal/app/donor_store.go                   |  12 ++
 internal/app/donor_store_test.go              | 113 +++++++-----
 internal/page/data.go                         |  23 ++-
 internal/page/data_test.go                    |  12 ++
 internal/page/donor/check_your_lpa.go         | 166 +++++++++++-------
 internal/page/donor/check_your_lpa_test.go    | 136 ++++++++++----
 internal/page/donor/mock_test.go              |   3 +
 internal/page/donor/register.go               |   2 +-
 internal/page/donor/upload_evidence_test.go   |   6 -
 .../donor/witnessing_your_signature_test.go   |   3 -
 internal/page/fixtures/donor.go               |   2 +-
 web/template/check_your_lpa.gohtml            |  70 ++++----
 15 files changed, 363 insertions(+), 203 deletions(-)

diff --git a/cypress/e2e/donor/check-your-lpa.cy.js b/cypress/e2e/donor/check-your-lpa.cy.js
index baaef95556..e452db9d83 100644
--- a/cypress/e2e/donor/check-your-lpa.cy.js
+++ b/cypress/e2e/donor/check-your-lpa.cy.js
@@ -34,6 +34,21 @@ describe('Check the LPA', () => {
         cy.url().should('contain', '/lpa-details-saved');
     });
 
+    it('does not allow checking when no changes', () => {
+        cy.get('#f-checked-and-happy').check()
+        cy.contains('button', 'Confirm').click();
+
+        cy.visitLpa('/check-your-lpa');
+        cy.contains('button', 'Confirm').should('not.exist');
+
+        cy.visitLpa('/restrictions');
+        cy.get('#f-restrictions').type('2');
+        cy.contains('button', 'Save and continue').click();
+
+        cy.visitLpa('/check-your-lpa');
+        cy.contains('button', 'Confirm');
+    });
+
     describe('CP acting on paper', () => {
         describe('on first check', () => {
             it('content is tailored for paper CPs, a details component is shown and nav redirects to payment', () => {
diff --git a/go.mod b/go.mod
index 8521f602f0..34c5b735aa 100644
--- a/go.mod
+++ b/go.mod
@@ -70,6 +70,7 @@ require (
 	github.com/hashicorp/go-version v1.5.0 // indirect
 	github.com/hashicorp/logutils v1.0.0 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/stretchr/objx v0.5.0 // indirect
 	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
diff --git a/go.sum b/go.sum
index 957d212ff0..e34a6dbcff 100644
--- a/go.sum
+++ b/go.sum
@@ -169,6 +169,8 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/ministryofjustice/opg-go-common v0.0.0-20231106092059-b3dcf8bd1eeb h1:nZ2pEcU9r5sAewHyQ0ZrXnPNLSHgfvxa7xf7rpZpbno=
 github.com/ministryofjustice/opg-go-common v0.0.0-20231106092059-b3dcf8bd1eeb/go.mod h1:qktwZb46YkojkLVHU2QNnVK6yVktXkNpBuJ+TyobvuY=
+github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
+github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
diff --git a/internal/app/donor_store.go b/internal/app/donor_store.go
index af7e0afdac..11745126f4 100644
--- a/internal/app/donor_store.go
+++ b/internal/app/donor_store.go
@@ -62,9 +62,14 @@ func (s *donorStore) Create(ctx context.Context) (*page.Lpa, error) {
 		Version:   1,
 	}
 
+	if lpa.Hash, err = lpa.GenerateHash(); err != nil {
+		return nil, err
+	}
+
 	if err := s.dynamoClient.Create(ctx, lpa); err != nil {
 		return nil, err
 	}
+
 	if err := s.dynamoClient.Create(ctx, lpaLink{
 		PK:        lpaKey(lpaID),
 		SK:        subKey(data.SessionID),
@@ -130,6 +135,13 @@ func (s *donorStore) Latest(ctx context.Context) (*page.Lpa, error) {
 }
 
 func (s *donorStore) Put(ctx context.Context, lpa *page.Lpa) error {
+	newHash, err := lpa.GenerateHash()
+	if newHash == lpa.Hash || err != nil {
+		return err
+	}
+
+	lpa.Hash = newHash
+
 	// By not setting UpdatedAt until a UID exists, queries for SK=#DONOR#xyz on
 	// ActorUpdatedAtIndex will not return UID-less LPAs.
 	if lpa.UID != "" {
diff --git a/internal/app/donor_store_test.go b/internal/app/donor_store_test.go
index ba83039adc..c1642882e9 100644
--- a/internal/app/donor_store_test.go
+++ b/internal/app/donor_store_test.go
@@ -14,11 +14,16 @@ import (
 	"github.com/ministryofjustice/opg-modernising-lpa/internal/page"
 	"github.com/ministryofjustice/opg-modernising-lpa/internal/place"
 	"github.com/ministryofjustice/opg-modernising-lpa/internal/uid"
+	"github.com/mitchellh/hashstructure/v2"
 	"github.com/stretchr/testify/assert"
 	mock "github.com/stretchr/testify/mock"
 )
 
-var expectedError = errors.New("err")
+var (
+	expectedError = errors.New("err")
+	testNow       = time.Date(2023, time.April, 2, 3, 4, 5, 6, time.UTC)
+	testNowFn     = func() time.Time { return testNow }
+)
 
 func (m *mockDynamoClient) ExpectOne(ctx, pk, sk, data interface{}, err error) {
 	m.
@@ -180,29 +185,30 @@ func TestDonorStoreLatestWhenDataStoreError(t *testing.T) {
 
 func TestDonorStorePut(t *testing.T) {
 	ctx := context.Background()
-	now := time.Now()
 
 	testcases := map[string]struct {
 		input, saved *page.Lpa
 	}{
 		"no uid": {
-			input: &page.Lpa{PK: "LPA#5", SK: "#DONOR#an-id", ID: "5", HasSentApplicationUpdatedEvent: true},
+			input: &page.Lpa{PK: "LPA#5", Hash: 5, SK: "#DONOR#an-id", ID: "5", HasSentApplicationUpdatedEvent: true},
 			saved: &page.Lpa{PK: "LPA#5", SK: "#DONOR#an-id", ID: "5", HasSentApplicationUpdatedEvent: true},
 		},
 		"with uid": {
-			input: &page.Lpa{PK: "LPA#5", SK: "#DONOR#an-id", ID: "5", HasSentApplicationUpdatedEvent: true, UID: "M"},
-			saved: &page.Lpa{PK: "LPA#5", SK: "#DONOR#an-id", ID: "5", HasSentApplicationUpdatedEvent: true, UID: "M", UpdatedAt: now},
+			input: &page.Lpa{PK: "LPA#5", Hash: 5, SK: "#DONOR#an-id", ID: "5", HasSentApplicationUpdatedEvent: true, UID: "M"},
+			saved: &page.Lpa{PK: "LPA#5", SK: "#DONOR#an-id", ID: "5", HasSentApplicationUpdatedEvent: true, UID: "M", UpdatedAt: testNow},
 		},
 	}
 
 	for name, tc := range testcases {
 		t.Run(name, func(t *testing.T) {
+			tc.saved.Hash, _ = tc.saved.GenerateHash()
+
 			dynamoClient := newMockDynamoClient(t)
 			dynamoClient.
 				On("Put", ctx, tc.saved).
 				Return(nil)
 
-			donorStore := &donorStore{dynamoClient: dynamoClient, now: func() time.Time { return now }}
+			donorStore := &donorStore{dynamoClient: dynamoClient, now: testNowFn}
 
 			err := donorStore.Put(ctx, tc.input)
 			assert.Nil(t, err)
@@ -210,6 +216,17 @@ func TestDonorStorePut(t *testing.T) {
 	}
 }
 
+func TestDonorStorePutWhenNoChange(t *testing.T) {
+	ctx := context.Background()
+	donorStore := &donorStore{}
+
+	lpa := &page.Lpa{ID: "an-id"}
+	lpa.Hash, _ = hashstructure.Hash(lpa, hashstructure.FormatV2, nil)
+
+	err := donorStore.Put(ctx, lpa)
+	assert.Nil(t, err)
+}
+
 func TestDonorStorePutWhenError(t *testing.T) {
 	ctx := context.Background()
 
@@ -224,7 +241,6 @@ func TestDonorStorePutWhenError(t *testing.T) {
 
 func TestDonorStorePutWhenUIDNeeded(t *testing.T) {
 	ctx := page.ContextWithSessionData(context.Background(), &page.SessionData{SessionID: "an-id"})
-	now := time.Now()
 
 	eventClient := newMockEventClient(t)
 	eventClient.
@@ -240,27 +256,30 @@ func TestDonorStorePutWhenUIDNeeded(t *testing.T) {
 		}).
 		Return(nil)
 
+	updatedLpa := &page.Lpa{
+		PK: "LPA#5",
+		SK: "#DONOR#an-id",
+		ID: "5",
+		Donor: actor.Donor{
+			FirstNames:  "John",
+			LastName:    "Smith",
+			DateOfBirth: date.New("2000", "01", "01"),
+			Address: place.Address{
+				Line1:    "line",
+				Postcode: "F1 1FF",
+			},
+		},
+		Type:                     page.LpaTypeHealthWelfare,
+		HasSentUidRequestedEvent: true,
+	}
+	updatedLpa.Hash, _ = updatedLpa.GenerateHash()
+
 	dynamoClient := newMockDynamoClient(t)
 	dynamoClient.
-		On("Put", ctx, &page.Lpa{
-			PK: "LPA#5",
-			SK: "#DONOR#an-id",
-			ID: "5",
-			Donor: actor.Donor{
-				FirstNames:  "John",
-				LastName:    "Smith",
-				DateOfBirth: date.New("2000", "01", "01"),
-				Address: place.Address{
-					Line1:    "line",
-					Postcode: "F1 1FF",
-				},
-			},
-			Type:                     page.LpaTypeHealthWelfare,
-			HasSentUidRequestedEvent: true,
-		}).
+		On("Put", ctx, updatedLpa).
 		Return(nil)
 
-	donorStore := &donorStore{dynamoClient: dynamoClient, eventClient: eventClient, now: func() time.Time { return now }}
+	donorStore := &donorStore{dynamoClient: dynamoClient, eventClient: eventClient}
 
 	err := donorStore.Put(ctx, &page.Lpa{
 		PK: "LPA#5",
@@ -335,14 +354,13 @@ func TestDonorStorePutWhenUIDFails(t *testing.T) {
 
 func TestDonorStorePutWhenApplicationUpdatedWhenError(t *testing.T) {
 	ctx := context.Background()
-	now := time.Now()
 
 	eventClient := newMockEventClient(t)
 	eventClient.
 		On("SendApplicationUpdated", ctx, mock.Anything).
 		Return(expectedError)
 
-	donorStore := &donorStore{eventClient: eventClient, now: func() time.Time { return now }}
+	donorStore := &donorStore{eventClient: eventClient, now: testNowFn}
 
 	err := donorStore.Put(ctx, &page.Lpa{
 		PK:  "LPA#5",
@@ -365,7 +383,6 @@ func TestDonorStorePutWhenApplicationUpdatedWhenError(t *testing.T) {
 
 func TestDonorStorePutWhenPreviousApplicationLinked(t *testing.T) {
 	ctx := context.Background()
-	now := time.Now()
 
 	eventClient := newMockEventClient(t)
 	eventClient.
@@ -375,21 +392,24 @@ func TestDonorStorePutWhenPreviousApplicationLinked(t *testing.T) {
 		}).
 		Return(nil)
 
+	updatedLpa := &page.Lpa{
+		PK:                                    "LPA#5",
+		SK:                                    "#DONOR#an-id",
+		ID:                                    "5",
+		UID:                                   "M-1111",
+		UpdatedAt:                             testNow,
+		PreviousApplicationNumber:             "5555",
+		HasSentApplicationUpdatedEvent:        true,
+		HasSentPreviousApplicationLinkedEvent: true,
+	}
+	updatedLpa.Hash, _ = updatedLpa.GenerateHash()
+
 	dynamoClient := newMockDynamoClient(t)
 	dynamoClient.
-		On("Put", ctx, &page.Lpa{
-			PK:                                    "LPA#5",
-			SK:                                    "#DONOR#an-id",
-			ID:                                    "5",
-			UID:                                   "M-1111",
-			UpdatedAt:                             now,
-			PreviousApplicationNumber:             "5555",
-			HasSentApplicationUpdatedEvent:        true,
-			HasSentPreviousApplicationLinkedEvent: true,
-		}).
+		On("Put", ctx, updatedLpa).
 		Return(nil)
 
-	donorStore := &donorStore{dynamoClient: dynamoClient, eventClient: eventClient, now: func() time.Time { return now }}
+	donorStore := &donorStore{dynamoClient: dynamoClient, eventClient: eventClient, now: testNowFn}
 
 	err := donorStore.Put(ctx, &page.Lpa{
 		PK:                             "LPA#5",
@@ -405,14 +425,13 @@ func TestDonorStorePutWhenPreviousApplicationLinked(t *testing.T) {
 
 func TestDonorStorePutWhenPreviousApplicationLinkedWontResend(t *testing.T) {
 	ctx := context.Background()
-	now := time.Now()
 
 	dynamoClient := newMockDynamoClient(t)
 	dynamoClient.
 		On("Put", ctx, mock.Anything).
 		Return(nil)
 
-	donorStore := &donorStore{dynamoClient: dynamoClient, now: func() time.Time { return now }}
+	donorStore := &donorStore{dynamoClient: dynamoClient, now: testNowFn}
 
 	err := donorStore.Put(ctx, &page.Lpa{
 		PK:                                    "LPA#5",
@@ -429,14 +448,13 @@ func TestDonorStorePutWhenPreviousApplicationLinkedWontResend(t *testing.T) {
 
 func TestDonorStorePutWhenPreviousApplicationLinkedWhenError(t *testing.T) {
 	ctx := context.Background()
-	now := time.Now()
 
 	eventClient := newMockEventClient(t)
 	eventClient.
 		On("SendPreviousApplicationLinked", ctx, mock.Anything).
 		Return(expectedError)
 
-	donorStore := &donorStore{eventClient: eventClient, now: func() time.Time { return now }}
+	donorStore := &donorStore{eventClient: eventClient, now: testNowFn}
 
 	err := donorStore.Put(ctx, &page.Lpa{
 		PK:                             "LPA#5",
@@ -451,18 +469,18 @@ func TestDonorStorePutWhenPreviousApplicationLinkedWhenError(t *testing.T) {
 
 func TestDonorStoreCreate(t *testing.T) {
 	ctx := page.ContextWithSessionData(context.Background(), &page.SessionData{SessionID: "an-id"})
-	now := time.Now()
-	lpa := &page.Lpa{PK: "LPA#10100000", SK: "#DONOR#an-id", ID: "10100000", CreatedAt: now, Version: 1}
+	lpa := &page.Lpa{PK: "LPA#10100000", SK: "#DONOR#an-id", ID: "10100000", CreatedAt: testNow, Version: 1}
+	lpa.Hash, _ = lpa.GenerateHash()
 
 	dynamoClient := newMockDynamoClient(t)
 	dynamoClient.
 		On("Create", ctx, lpa).
 		Return(nil)
 	dynamoClient.
-		On("Create", ctx, lpaLink{PK: "LPA#10100000", SK: "#SUB#an-id", DonorKey: "#DONOR#an-id", ActorType: actor.TypeDonor, UpdatedAt: now}).
+		On("Create", ctx, lpaLink{PK: "LPA#10100000", SK: "#SUB#an-id", DonorKey: "#DONOR#an-id", ActorType: actor.TypeDonor, UpdatedAt: testNow}).
 		Return(nil)
 
-	donorStore := &donorStore{dynamoClient: dynamoClient, uuidString: func() string { return "10100000" }, now: func() time.Time { return now }}
+	donorStore := &donorStore{dynamoClient: dynamoClient, uuidString: func() string { return "10100000" }, now: testNowFn}
 
 	result, err := donorStore.Create(ctx)
 	assert.Nil(t, err)
@@ -480,7 +498,6 @@ func TestDonorStoreCreateWithSessionMissing(t *testing.T) {
 
 func TestDonorStoreCreateWhenError(t *testing.T) {
 	ctx := page.ContextWithSessionData(context.Background(), &page.SessionData{SessionID: "an-id"})
-	now := time.Now()
 
 	testcases := map[string]func(*testing.T) *mockDynamoClient{
 		"certificate provider record": func(t *testing.T) *mockDynamoClient {
@@ -512,7 +529,7 @@ func TestDonorStoreCreateWhenError(t *testing.T) {
 			donorStore := &donorStore{
 				dynamoClient: dynamoClient,
 				uuidString:   func() string { return "10100000" },
-				now:          func() time.Time { return now },
+				now:          testNowFn,
 			}
 
 			_, err := donorStore.Create(ctx)
diff --git a/internal/page/data.go b/internal/page/data.go
index 7971315cc5..9fa4d62b40 100644
--- a/internal/page/data.go
+++ b/internal/page/data.go
@@ -21,6 +21,7 @@ import (
 	"github.com/ministryofjustice/opg-modernising-lpa/internal/identity"
 	"github.com/ministryofjustice/opg-modernising-lpa/internal/pay"
 	"github.com/ministryofjustice/opg-modernising-lpa/internal/place"
+	"github.com/mitchellh/hashstructure/v2"
 	"golang.org/x/exp/slices"
 )
 
@@ -70,6 +71,8 @@ const (
 // Lpa contains all the data related to the LPA application
 type Lpa struct {
 	PK, SK string
+	// Hash is used to determine whether the Lpa has been changed since last read
+	Hash uint64 `hash:"-"`
 	// Identifies the LPA being drafted
 	ID string
 	// A unique identifier created after sending basic LPA details to the UID service
@@ -77,7 +80,7 @@ type Lpa struct {
 	// CreatedAt is when the LPA was created
 	CreatedAt time.Time
 	// UpdatedAt is when the LPA was last updated
-	UpdatedAt time.Time
+	UpdatedAt time.Time `hash:"-"`
 	// The donor the LPA relates to
 	Donor actor.Donor
 	// Attorneys named in the LPA
@@ -98,8 +101,6 @@ type Lpa struct {
 	Restrictions string
 	// Used to show the task list
 	Tasks Tasks
-	// Whether the applicant has checked the LPA and is happy to share the LPA with the certificate provider
-	CheckedAndHappy bool
 	// PaymentDetails are records of payments made for the LPA via GOV.UK Pay
 	PaymentDetails []Payment
 	// Information returned by the identity service related to the applicant
@@ -124,6 +125,10 @@ type Lpa struct {
 	WantToApplyForLpa bool
 	// Confirmation that the applicant wants to sign the LPA
 	WantToSignLpa bool
+	// CheckedAt is when the donor checked their LPA
+	CheckedAt time.Time
+	// CheckedHash is the Hash value of the LPA when last checked
+	CheckedHash uint64 `hash:"-"`
 	// SignedAt is when the donor submitted their signature
 	SignedAt time.Time
 	// SubmittedAt is when the Lpa was sent to the OPG
@@ -133,7 +138,7 @@ type Lpa struct {
 	// WithdrawnAt is when the Lpa was withdrawn by the donor
 	WithdrawnAt time.Time
 	// Version is the number of times the LPA has been updated (auto-incremented on PUT)
-	Version int
+	Version int `hash:"-"`
 
 	// Codes used for the certificate provider to witness signing
 	CertificateProviderCodes WitnessCodes
@@ -155,9 +160,9 @@ type Lpa struct {
 	// PreviousFee is the fee previously paid for an LPA
 	PreviousFee pay.PreviousFee
 
-	HasSentUidRequestedEvent              bool
-	HasSentApplicationUpdatedEvent        bool
-	HasSentPreviousApplicationLinkedEvent bool
+	HasSentUidRequestedEvent              bool `hash:"-"`
+	HasSentApplicationUpdatedEvent        bool `hash:"-"`
+	HasSentPreviousApplicationLinkedEvent bool `hash:"-"`
 }
 
 type Payment struct {
@@ -218,6 +223,10 @@ func ContextWithSessionData(ctx context.Context, data *SessionData) context.Cont
 	return context.WithValue(ctx, (*SessionData)(nil), data)
 }
 
+func (l *Lpa) GenerateHash() (uint64, error) {
+	return hashstructure.Hash(l, hashstructure.FormatV2, nil)
+}
+
 func (l *Lpa) DonorIdentityConfirmed() bool {
 	return l.DonorIdentityUserData.OK &&
 		l.DonorIdentityUserData.MatchName(l.Donor.FirstNames, l.Donor.LastName) &&
diff --git a/internal/page/data_test.go b/internal/page/data_test.go
index d462ccb9fe..095f454fc0 100644
--- a/internal/page/data_test.go
+++ b/internal/page/data_test.go
@@ -147,6 +147,18 @@ func TestReplacementAttorneysStepIn(t *testing.T) {
 	})
 }
 
+func TestGenerateHash(t *testing.T) {
+	lpa := &Lpa{}
+	hash, err := lpa.GenerateHash()
+	assert.Nil(t, err)
+	assert.Equal(t, uint64(0x53e4ea75485b238f), hash)
+
+	lpa.ID = "1"
+	hash, err = lpa.GenerateHash()
+	assert.Nil(t, err)
+	assert.Equal(t, uint64(0x8dbcd47c6136ec6f), hash)
+}
+
 func TestIdentityConfirmed(t *testing.T) {
 	testCases := map[string]struct {
 		lpa      *Lpa
diff --git a/internal/page/donor/check_your_lpa.go b/internal/page/donor/check_your_lpa.go
index cbc5905b22..06ac36a2cf 100644
--- a/internal/page/donor/check_your_lpa.go
+++ b/internal/page/donor/check_your_lpa.go
@@ -1,8 +1,10 @@
 package donor
 
 import (
+	"context"
 	"errors"
 	"net/http"
+	"time"
 
 	"github.com/ministryofjustice/opg-go-common/template"
 	"github.com/ministryofjustice/opg-modernising-lpa/internal/actor"
@@ -13,25 +15,105 @@ import (
 )
 
 type checkYourLpaData struct {
-	App       page.AppData
-	Errors    validation.List
-	Lpa       *page.Lpa
-	Form      *checkYourLpaForm
-	Completed bool
+	App         page.AppData
+	Errors      validation.List
+	Lpa         *page.Lpa
+	Form        *checkYourLpaForm
+	Completed   bool
+	CanContinue bool
 }
 
-func CheckYourLpa(tmpl template.Template, donorStore DonorStore, shareCodeSender ShareCodeSender, notifyClient NotifyClient, certificateProviderStore CertificateProviderStore) Handler {
+type checkYourLpaNotifier struct {
+	notifyClient             NotifyClient
+	shareCodeSender          ShareCodeSender
+	certificateProviderStore CertificateProviderStore
+}
+
+func (n *checkYourLpaNotifier) Notify(ctx context.Context, appData page.AppData, lpa *page.Lpa, wasCompleted bool) error {
+	if lpa.CertificateProvider.CarryOutBy.IsPaper() {
+		err := n.sendPaperNotification(ctx, appData, lpa, wasCompleted)
+		return err
+	}
+
+	err := n.sendOnlineNotification(ctx, appData, lpa, wasCompleted)
+	return err
+}
+
+func (n *checkYourLpaNotifier) sendPaperNotification(ctx context.Context, appData page.AppData, lpa *page.Lpa, wasCompleted bool) error {
+	sms := notify.Sms{
+		PhoneNumber: lpa.CertificateProvider.Mobile,
+		Personalisation: map[string]string{
+			"donorFullName":   lpa.Donor.FullName(),
+			"donorFirstNames": lpa.Donor.FirstNames,
+		},
+	}
+
+	if wasCompleted {
+		sms.TemplateID = n.notifyClient.TemplateID(notify.CertificateProviderPaperLpaDetailsChangedSMS)
+		sms.Personalisation["lpaId"] = lpa.ID
+	} else {
+		sms.TemplateID = n.notifyClient.TemplateID(notify.CertificateProviderPaperMeetingPromptSMS)
+		sms.Personalisation["lpaType"] = appData.Localizer.T(lpa.Type.LegalTermTransKey())
+	}
+
+	_, err := n.notifyClient.Sms(ctx, sms)
+	return err
+}
+
+func (n *checkYourLpaNotifier) sendOnlineNotification(ctx context.Context, appData page.AppData, lpa *page.Lpa, wasCompleted bool) error {
+	if !wasCompleted {
+		err := n.shareCodeSender.SendCertificateProvider(ctx, notify.CertificateProviderInviteEmail, appData, true, lpa)
+		return err
+	}
+
+	certificateProvider, err := n.certificateProviderStore.GetAny(ctx)
+	if err != nil && !errors.Is(err, dynamo.NotFoundError{}) {
+		return err
+	}
+
+	sms := notify.Sms{
+		PhoneNumber: lpa.CertificateProvider.Mobile,
+	}
+
+	if certificateProvider.Tasks.ConfirmYourDetails.NotStarted() {
+		sms.TemplateID = n.notifyClient.TemplateID(notify.CertificateProviderDigitalLpaDetailsChangedNotSeenLpaSMS)
+		sms.Personalisation = map[string]string{
+			"donorFullName": lpa.Donor.FullName(),
+			"lpaType":       appData.Localizer.T(lpa.Type.LegalTermTransKey()),
+		}
+	} else {
+		sms.TemplateID = n.notifyClient.TemplateID(notify.CertificateProviderDigitalLpaDetailsChangedSeenLpaSMS)
+		sms.Personalisation = map[string]string{
+			"donorFullNamePossessive": appData.Localizer.Possessive(lpa.Donor.FullName()),
+			"lpaType":                 appData.Localizer.T(lpa.Type.LegalTermTransKey()),
+			"lpaId":                   lpa.ID,
+			"donorFirstNames":         lpa.Donor.FirstNames,
+		}
+	}
+
+	_, err = n.notifyClient.Sms(ctx, sms)
+	return err
+}
+
+func CheckYourLpa(tmpl template.Template, donorStore DonorStore, shareCodeSender ShareCodeSender, notifyClient NotifyClient, certificateProviderStore CertificateProviderStore, now func() time.Time) Handler {
+	notifier := &checkYourLpaNotifier{
+		notifyClient:             notifyClient,
+		shareCodeSender:          shareCodeSender,
+		certificateProviderStore: certificateProviderStore,
+	}
+
 	return func(appData page.AppData, w http.ResponseWriter, r *http.Request, lpa *page.Lpa) error {
 		data := &checkYourLpaData{
 			App: appData,
 			Lpa: lpa,
 			Form: &checkYourLpaForm{
-				CheckedAndHappy: lpa.CheckedAndHappy,
+				CheckedAndHappy: !lpa.CheckedAt.IsZero(),
 			},
-			Completed: lpa.Tasks.CheckYourLpa.Completed(),
+			Completed:   lpa.Tasks.CheckYourLpa.Completed(),
+			CanContinue: lpa.CheckedHash != lpa.Hash,
 		}
 
-		if r.Method == http.MethodPost {
+		if r.Method == http.MethodPost && data.CanContinue {
 			data.Form = readCheckYourLpaForm(r)
 			data.Errors = data.Form.Validate()
 
@@ -42,69 +124,21 @@ func CheckYourLpa(tmpl template.Template, donorStore DonorStore, shareCodeSender
 					redirect = redirect + "?firstCheck=1"
 				}
 
-				lpa.CheckedAndHappy = data.Form.CheckedAndHappy
 				lpa.Tasks.CheckYourLpa = actor.TaskCompleted
+				lpa.CheckedAt = now()
+
+				newHash, err := lpa.GenerateHash()
+				if err != nil {
+					return err
+				}
+				lpa.CheckedHash = newHash
 
 				if err := donorStore.Put(r.Context(), lpa); err != nil {
 					return err
 				}
 
-				if lpa.CertificateProvider.CarryOutBy.IsPaper() {
-					sms := notify.Sms{
-						PhoneNumber: lpa.CertificateProvider.Mobile,
-						Personalisation: map[string]string{
-							"donorFullName":   lpa.Donor.FullName(),
-							"donorFirstNames": lpa.Donor.FirstNames,
-						},
-					}
-
-					if data.Completed {
-						sms.TemplateID = notifyClient.TemplateID(notify.CertificateProviderPaperLpaDetailsChangedSMS)
-						sms.Personalisation["lpaId"] = lpa.ID
-					} else {
-						sms.TemplateID = notifyClient.TemplateID(notify.CertificateProviderPaperMeetingPromptSMS)
-						sms.Personalisation["lpaType"] = appData.Localizer.T(lpa.Type.LegalTermTransKey())
-					}
-
-					if _, err := notifyClient.Sms(r.Context(), sms); err != nil {
-						return err
-					}
-				} else {
-					if data.Completed {
-						certificateProvider, err := certificateProviderStore.GetAny(r.Context())
-
-						if err != nil && !errors.Is(err, dynamo.NotFoundError{}) {
-							return err
-						}
-
-						sms := notify.Sms{
-							PhoneNumber: lpa.CertificateProvider.Mobile,
-						}
-
-						if certificateProvider.Tasks.ConfirmYourDetails.NotStarted() {
-							sms.TemplateID = notifyClient.TemplateID(notify.CertificateProviderDigitalLpaDetailsChangedNotSeenLpaSMS)
-							sms.Personalisation = map[string]string{
-								"donorFullName": lpa.Donor.FullName(),
-								"lpaType":       appData.Localizer.T(lpa.Type.LegalTermTransKey()),
-							}
-						} else {
-							sms.TemplateID = notifyClient.TemplateID(notify.CertificateProviderDigitalLpaDetailsChangedSeenLpaSMS)
-							sms.Personalisation = map[string]string{
-								"donorFullNamePossessive": appData.Localizer.Possessive(lpa.Donor.FullName()),
-								"lpaType":                 appData.Localizer.T(lpa.Type.LegalTermTransKey()),
-								"lpaId":                   lpa.ID,
-								"donorFirstNames":         lpa.Donor.FirstNames,
-							}
-						}
-
-						if _, err := notifyClient.Sms(r.Context(), sms); err != nil {
-							return err
-						}
-					} else {
-						if err := shareCodeSender.SendCertificateProvider(r.Context(), notify.CertificateProviderInviteEmail, appData, true, lpa); err != nil {
-							return err
-						}
-					}
+				if err := notifier.Notify(r.Context(), appData, lpa, data.Completed); err != nil {
+					return err
 				}
 
 				return appData.Redirect(w, r, lpa, redirect)
diff --git a/internal/page/donor/check_your_lpa_test.go b/internal/page/donor/check_your_lpa_test.go
index 99038d17e1..e13348a1eb 100644
--- a/internal/page/donor/check_your_lpa_test.go
+++ b/internal/page/donor/check_your_lpa_test.go
@@ -28,7 +28,7 @@ func TestGetCheckYourLpa(t *testing.T) {
 		}).
 		Return(nil)
 
-	err := CheckYourLpa(template.Execute, nil, nil, nil, nil)(testAppData, w, r, &page.Lpa{})
+	err := CheckYourLpa(template.Execute, nil, nil, nil, nil, testNowFn)(testAppData, w, r, &page.Lpa{})
 	resp := w.Result()
 
 	assert.Nil(t, err)
@@ -40,7 +40,7 @@ func TestGetCheckYourLpaFromStore(t *testing.T) {
 	r, _ := http.NewRequest(http.MethodGet, "/", nil)
 
 	lpa := &page.Lpa{
-		CheckedAndHappy: true,
+		CheckedAt: testNow,
 	}
 
 	template := newMockTemplate(t)
@@ -54,7 +54,44 @@ func TestGetCheckYourLpaFromStore(t *testing.T) {
 		}).
 		Return(nil)
 
-	err := CheckYourLpa(template.Execute, nil, nil, nil, nil)(testAppData, w, r, lpa)
+	err := CheckYourLpa(template.Execute, nil, nil, nil, nil, testNowFn)(testAppData, w, r, lpa)
+	resp := w.Result()
+
+	assert.Nil(t, err)
+	assert.Equal(t, http.StatusOK, resp.StatusCode)
+}
+
+func TestPostCheckYourLpaWhenNotChanged(t *testing.T) {
+	form := url.Values{
+		"checked-and-happy": {"1"},
+	}
+
+	w := httptest.NewRecorder()
+	r, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(form.Encode()))
+	r.Header.Add("Content-Type", page.FormUrlEncoded)
+
+	lpa := &page.Lpa{
+		ID:                  "lpa-id",
+		Hash:                5,
+		CheckedAt:           testNow,
+		CheckedHash:         5,
+		Tasks:               page.Tasks{CheckYourLpa: actor.TaskCompleted},
+		CertificateProvider: actor.CertificateProvider{CarryOutBy: actor.Online},
+	}
+
+	template := newMockTemplate(t)
+	template.
+		On("Execute", w, &checkYourLpaData{
+			App: testAppData,
+			Lpa: lpa,
+			Form: &checkYourLpaForm{
+				CheckedAndHappy: true,
+			},
+			Completed: true,
+		}).
+		Return(nil)
+
+	err := CheckYourLpa(template.Execute, nil, nil, nil, nil, testNowFn)(testAppData, w, r, lpa)
 	resp := w.Result()
 
 	assert.Nil(t, err)
@@ -79,17 +116,19 @@ func TestPostCheckYourLpaDigitalCertificateProviderOnFirstCheck(t *testing.T) {
 
 			lpa := &page.Lpa{
 				ID:                  "lpa-id",
-				CheckedAndHappy:     false,
+				Hash:                5,
 				Tasks:               page.Tasks{CheckYourLpa: existingTaskState},
 				CertificateProvider: actor.CertificateProvider{CarryOutBy: actor.Online},
 			}
 
 			updatedLpa := &page.Lpa{
 				ID:                  "lpa-id",
-				CheckedAndHappy:     true,
+				Hash:                5,
+				CheckedAt:           testNow,
 				Tasks:               page.Tasks{CheckYourLpa: actor.TaskCompleted},
 				CertificateProvider: actor.CertificateProvider{CarryOutBy: actor.Online},
 			}
+			updatedLpa.CheckedHash, _ = updatedLpa.GenerateHash()
 
 			shareCodeSender := newMockShareCodeSender(t)
 			shareCodeSender.
@@ -101,7 +140,7 @@ func TestPostCheckYourLpaDigitalCertificateProviderOnFirstCheck(t *testing.T) {
 				On("Put", r.Context(), updatedLpa).
 				Return(nil)
 
-			err := CheckYourLpa(nil, donorStore, shareCodeSender, nil, nil)(testAppData, w, r, lpa)
+			err := CheckYourLpa(nil, donorStore, shareCodeSender, nil, nil, testNowFn)(testAppData, w, r, lpa)
 			resp := w.Result()
 
 			assert.Nil(t, err)
@@ -182,9 +221,10 @@ func TestPostCheckYourLpaDigitalCertificateProviderOnSubsequentChecks(t *testing
 
 			lpa := &page.Lpa{
 				ID:                  "lpa-id",
+				Hash:                5,
 				Type:                page.LpaTypePropertyFinance,
 				Donor:               actor.Donor{FirstNames: "Teneil", LastName: "Throssell"},
-				CheckedAndHappy:     true,
+				CheckedAt:           testNow,
 				Tasks:               page.Tasks{CheckYourLpa: actor.TaskCompleted},
 				CertificateProvider: actor.CertificateProvider{CarryOutBy: actor.Online, Mobile: "07700900000"},
 			}
@@ -209,7 +249,7 @@ func TestPostCheckYourLpaDigitalCertificateProviderOnSubsequentChecks(t *testing
 					Tasks: actor.CertificateProviderTasks{ConfirmYourDetails: tc.certificateProviderDetailsTaskState},
 				}, nil)
 
-			err := CheckYourLpa(nil, donorStore, nil, notifyClient, certificateProviderStore)(testAppData, w, r, lpa)
+			err := CheckYourLpa(nil, donorStore, nil, notifyClient, certificateProviderStore, testNowFn)(testAppData, w, r, lpa)
 			resp := w.Result()
 
 			assert.Nil(t, err)
@@ -219,13 +259,39 @@ func TestPostCheckYourLpaDigitalCertificateProviderOnSubsequentChecks(t *testing
 	}
 }
 
-func TestPostCheckYourLpaPaperCertificateProviderOnFirstCheck(t *testing.T) {
-	testCases := map[actor.TaskState]string{
-		actor.TaskNotStarted: page.Paths.LpaDetailsSaved.Format("lpa-id") + "?firstCheck=1",
-		actor.TaskInProgress: page.Paths.LpaDetailsSaved.Format("lpa-id") + "?firstCheck=1",
+func TestPostCheckYourLpaDigitalCertificateProviderOnSubsequentChecksCertificateProviderStoreErrors(t *testing.T) {
+	form := url.Values{
+		"checked-and-happy": {"1"},
 	}
 
-	for existingTaskState, expectedURL := range testCases {
+	w := httptest.NewRecorder()
+	r, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(form.Encode()))
+	r.Header.Add("Content-Type", page.FormUrlEncoded)
+
+	donorStore := newMockDonorStore(t)
+	donorStore.
+		On("Put", r.Context(), mock.Anything).
+		Return(nil)
+
+	certificateProviderStore := newMockCertificateProviderStore(t)
+	certificateProviderStore.
+		On("GetAny", r.Context()).
+		Return(nil, expectedError)
+
+	err := CheckYourLpa(nil, donorStore, nil, nil, certificateProviderStore, testNowFn)(testAppData, w, r, &page.Lpa{
+		ID:                  "lpa-id",
+		Hash:                5,
+		Type:                page.LpaTypePropertyFinance,
+		Donor:               actor.Donor{FirstNames: "Teneil", LastName: "Throssell"},
+		CheckedAt:           testNow,
+		Tasks:               page.Tasks{CheckYourLpa: actor.TaskCompleted},
+		CertificateProvider: actor.CertificateProvider{CarryOutBy: actor.Online, Mobile: "07700900000"},
+	})
+	assert.Equal(t, expectedError, err)
+}
+
+func TestPostCheckYourLpaPaperCertificateProviderOnFirstCheck(t *testing.T) {
+	for _, existingTaskState := range []actor.TaskState{actor.TaskNotStarted, actor.TaskInProgress} {
 		t.Run(existingTaskState.String(), func(t *testing.T) {
 			form := url.Values{
 				"checked-and-happy": {"1"},
@@ -244,8 +310,8 @@ func TestPostCheckYourLpaPaperCertificateProviderOnFirstCheck(t *testing.T) {
 
 			lpa := &page.Lpa{
 				ID:                  "lpa-id",
+				Hash:                5,
 				Donor:               actor.Donor{FirstNames: "Teneil", LastName: "Throssell"},
-				CheckedAndHappy:     false,
 				Tasks:               page.Tasks{CheckYourLpa: existingTaskState},
 				CertificateProvider: actor.CertificateProvider{CarryOutBy: actor.Paper, Mobile: "07700900000"},
 				Type:                page.LpaTypePropertyFinance,
@@ -253,12 +319,14 @@ func TestPostCheckYourLpaPaperCertificateProviderOnFirstCheck(t *testing.T) {
 
 			updatedLpa := &page.Lpa{
 				ID:                  "lpa-id",
+				Hash:                5,
 				Donor:               actor.Donor{FirstNames: "Teneil", LastName: "Throssell"},
-				CheckedAndHappy:     true,
+				CheckedAt:           testNow,
 				Tasks:               page.Tasks{CheckYourLpa: actor.TaskCompleted},
 				CertificateProvider: actor.CertificateProvider{CarryOutBy: actor.Paper, Mobile: "07700900000"},
 				Type:                page.LpaTypePropertyFinance,
 			}
+			updatedLpa.CheckedHash, _ = updatedLpa.GenerateHash()
 
 			donorStore := newMockDonorStore(t)
 			donorStore.
@@ -281,12 +349,12 @@ func TestPostCheckYourLpaPaperCertificateProviderOnFirstCheck(t *testing.T) {
 				}).
 				Return("", nil)
 
-			err := CheckYourLpa(nil, donorStore, nil, notifyClient, nil)(testAppData, w, r, lpa)
+			err := CheckYourLpa(nil, donorStore, nil, notifyClient, nil, testNowFn)(testAppData, w, r, lpa)
 			resp := w.Result()
 
 			assert.Nil(t, err)
 			assert.Equal(t, http.StatusFound, resp.StatusCode)
-			assert.Equal(t, expectedURL, resp.Header.Get("Location"))
+			assert.Equal(t, page.Paths.LpaDetailsSaved.Format("lpa-id")+"?firstCheck=1", resp.Header.Get("Location"))
 		})
 	}
 }
@@ -302,8 +370,9 @@ func TestPostCheckYourLpaPaperCertificateProviderOnSubsequentCheck(t *testing.T)
 
 	lpa := &page.Lpa{
 		ID:                  "lpa-id",
+		Hash:                5,
 		Donor:               actor.Donor{FirstNames: "Teneil", LastName: "Throssell"},
-		CheckedAndHappy:     true,
+		CheckedAt:           testNow,
 		Tasks:               page.Tasks{CheckYourLpa: actor.TaskCompleted},
 		CertificateProvider: actor.CertificateProvider{CarryOutBy: actor.Paper, Mobile: "07700900000"},
 		Type:                page.LpaTypePropertyFinance,
@@ -330,7 +399,7 @@ func TestPostCheckYourLpaPaperCertificateProviderOnSubsequentCheck(t *testing.T)
 		}).
 		Return("", nil)
 
-	err := CheckYourLpa(nil, donorStore, nil, notifyClient, nil)(testAppData, w, r, lpa)
+	err := CheckYourLpa(nil, donorStore, nil, notifyClient, nil, testNowFn)(testAppData, w, r, lpa)
 	resp := w.Result()
 
 	assert.Nil(t, err)
@@ -349,13 +418,10 @@ func TestPostCheckYourLpaWhenStoreErrors(t *testing.T) {
 
 	donorStore := newMockDonorStore(t)
 	donorStore.
-		On("Put", r.Context(), &page.Lpa{
-			CheckedAndHappy: true,
-			Tasks:           page.Tasks{CheckYourLpa: actor.TaskCompleted},
-		}).
+		On("Put", r.Context(), mock.Anything).
 		Return(expectedError)
 
-	err := CheckYourLpa(nil, donorStore, nil, nil, nil)(testAppData, w, r, &page.Lpa{})
+	err := CheckYourLpa(nil, donorStore, nil, nil, nil, testNowFn)(testAppData, w, r, &page.Lpa{Hash: 5})
 	resp := w.Result()
 
 	assert.Equal(t, expectedError, err)
@@ -372,28 +438,22 @@ func TestPostCheckYourLpaWhenShareCodeSenderErrors(t *testing.T) {
 	r.Header.Add("Content-Type", page.FormUrlEncoded)
 
 	lpa := &page.Lpa{
-		ID:              "lpa-id",
-		CheckedAndHappy: false,
-		Tasks:           page.Tasks{CheckYourLpa: actor.TaskInProgress},
-	}
-
-	updatedLpa := &page.Lpa{
-		ID:              "lpa-id",
-		CheckedAndHappy: true,
-		Tasks:           page.Tasks{CheckYourLpa: actor.TaskCompleted},
+		ID:    "lpa-id",
+		Hash:  5,
+		Tasks: page.Tasks{CheckYourLpa: actor.TaskInProgress},
 	}
 
 	donorStore := newMockDonorStore(t)
 	donorStore.
-		On("Put", r.Context(), updatedLpa).
+		On("Put", r.Context(), mock.Anything).
 		Return(nil)
 
 	shareCodeSender := newMockShareCodeSender(t)
 	shareCodeSender.
-		On("SendCertificateProvider", r.Context(), notify.CertificateProviderInviteEmail, testAppData, true, updatedLpa).
+		On("SendCertificateProvider", r.Context(), notify.CertificateProviderInviteEmail, testAppData, true, mock.Anything).
 		Return(expectedError)
 
-	err := CheckYourLpa(nil, donorStore, shareCodeSender, nil, nil)(testAppData, w, r, lpa)
+	err := CheckYourLpa(nil, donorStore, shareCodeSender, nil, nil, testNowFn)(testAppData, w, r, lpa)
 	resp := w.Result()
 
 	assert.Equal(t, expectedError, err)
@@ -429,7 +489,7 @@ func TestPostCheckYourLpaWhenNotifyClientErrors(t *testing.T) {
 		On("Sms", mock.Anything, mock.Anything).
 		Return("", expectedError)
 
-	err := CheckYourLpa(nil, donorStore, nil, notifyClient, nil)(testAppData, w, r, &page.Lpa{CertificateProvider: actor.CertificateProvider{CarryOutBy: actor.Paper}})
+	err := CheckYourLpa(nil, donorStore, nil, notifyClient, nil, testNowFn)(testAppData, w, r, &page.Lpa{Hash: 5, CertificateProvider: actor.CertificateProvider{CarryOutBy: actor.Paper}})
 	resp := w.Result()
 
 	assert.Equal(t, expectedError, err)
@@ -452,7 +512,7 @@ func TestPostCheckYourLpaWhenValidationErrors(t *testing.T) {
 		})).
 		Return(nil)
 
-	err := CheckYourLpa(template.Execute, nil, nil, nil, nil)(testAppData, w, r, &page.Lpa{})
+	err := CheckYourLpa(template.Execute, nil, nil, nil, nil, nil)(testAppData, w, r, &page.Lpa{Hash: 5})
 	resp := w.Result()
 
 	assert.Nil(t, err)
diff --git a/internal/page/donor/mock_test.go b/internal/page/donor/mock_test.go
index dced2c9cfb..23db1c7d7b 100644
--- a/internal/page/donor/mock_test.go
+++ b/internal/page/donor/mock_test.go
@@ -4,6 +4,7 @@ import (
 	"errors"
 	"net/http"
 	"net/http/httptest"
+	"time"
 
 	"github.com/gorilla/sessions"
 	"github.com/ministryofjustice/opg-modernising-lpa/internal/actor"
@@ -31,6 +32,8 @@ var (
 		Lang:      localize.En,
 		Paths:     page.Paths,
 	}
+	testNow   = time.Date(2023, time.July, 3, 4, 5, 6, 1, time.UTC)
+	testNowFn = func() time.Time { return testNow }
 )
 
 func (m *mockDonorStore) willReturnEmptyLpa(r *http.Request) *mockDonorStore {
diff --git a/internal/page/donor/register.go b/internal/page/donor/register.go
index abfccaeb9b..e25b3eb692 100644
--- a/internal/page/donor/register.go
+++ b/internal/page/donor/register.go
@@ -308,7 +308,7 @@ func Register(
 	handleWithLpa(page.Paths.ConfirmYourCertificateProviderIsNotRelated, CanGoBack,
 		ConfirmYourCertificateProviderIsNotRelated(tmpls.Get("confirm_your_certificate_provider_is_not_related.gohtml"), donorStore))
 	handleWithLpa(page.Paths.CheckYourLpa, CanGoBack,
-		CheckYourLpa(tmpls.Get("check_your_lpa.gohtml"), donorStore, shareCodeSender, notifyClient, certificateProviderStore))
+		CheckYourLpa(tmpls.Get("check_your_lpa.gohtml"), donorStore, shareCodeSender, notifyClient, certificateProviderStore, time.Now))
 	handleWithLpa(page.Paths.LpaDetailsSaved, CanGoBack,
 		LpaDetailsSaved(tmpls.Get("lpa_details_saved.gohtml")))
 
diff --git a/internal/page/donor/upload_evidence_test.go b/internal/page/donor/upload_evidence_test.go
index bda8a59ef8..cecd3f3eea 100644
--- a/internal/page/donor/upload_evidence_test.go
+++ b/internal/page/donor/upload_evidence_test.go
@@ -10,7 +10,6 @@ import (
 	"os"
 	"strings"
 	"testing"
-	"time"
 
 	"github.com/ministryofjustice/opg-modernising-lpa/internal/actor"
 	"github.com/ministryofjustice/opg-modernising-lpa/internal/page"
@@ -20,11 +19,6 @@ import (
 	"github.com/stretchr/testify/mock"
 )
 
-var (
-	testNow   = time.Now()
-	testNowFn = func() time.Time { return testNow }
-)
-
 func TestGetUploadEvidence(t *testing.T) {
 	w := httptest.NewRecorder()
 	r, _ := http.NewRequest(http.MethodGet, "/", nil)
diff --git a/internal/page/donor/witnessing_your_signature_test.go b/internal/page/donor/witnessing_your_signature_test.go
index 1c9bbb0821..08c6708d78 100644
--- a/internal/page/donor/witnessing_your_signature_test.go
+++ b/internal/page/donor/witnessing_your_signature_test.go
@@ -79,7 +79,6 @@ func TestPostWitnessingYourSignatureCannotSign(t *testing.T) {
 	r, _ := http.NewRequest(http.MethodPost, "/", nil)
 
 	lpa := &page.Lpa{
-		Version:               1,
 		ID:                    "lpa-id",
 		Donor:                 actor.Donor{CanSign: form.No},
 		DonorIdentityUserData: identity.UserData{OK: true},
@@ -92,7 +91,6 @@ func TestPostWitnessingYourSignatureCannotSign(t *testing.T) {
 		Return(nil)
 	witnessCodeSender.
 		On("SendToIndependentWitness", r.Context(), &page.Lpa{
-			Version:               2,
 			ID:                    "lpa-id",
 			Donor:                 actor.Donor{CanSign: form.No},
 			DonorIdentityUserData: identity.UserData{OK: true},
@@ -104,7 +102,6 @@ func TestPostWitnessingYourSignatureCannotSign(t *testing.T) {
 	donorStore.
 		On("Get", r.Context()).
 		Return(&page.Lpa{
-			Version:               2,
 			ID:                    "lpa-id",
 			Donor:                 actor.Donor{CanSign: form.No},
 			DonorIdentityUserData: identity.UserData{OK: true},
diff --git a/internal/page/fixtures/donor.go b/internal/page/fixtures/donor.go
index a1c1c2733e..d3e4d01f91 100644
--- a/internal/page/fixtures/donor.go
+++ b/internal/page/fixtures/donor.go
@@ -196,7 +196,7 @@ func Donor(
 		}
 
 		if progress >= slices.Index(progressValues, "checkAndSendToYourCertificateProvider") {
-			lpa.CheckedAndHappy = true
+			lpa.CheckedAt = time.Now()
 			lpa.Tasks.CheckYourLpa = actor.TaskCompleted
 		}
 
diff --git a/web/template/check_your_lpa.gohtml b/web/template/check_your_lpa.gohtml
index 608f0e35c1..e5bfdcb6dc 100644
--- a/web/template/check_your_lpa.gohtml
+++ b/web/template/check_your_lpa.gohtml
@@ -23,45 +23,49 @@
 
       {{ template "people-named-on-lpa" . }}
 
-      <form novalidate method="post">
-        <div class="govuk-form-group  {{ if .Errors.Has "checked-and-happy" }}govuk-form-group--error{{ end }}">
-          {{ template "error-message" (errorMessage . "checked-and-happy") }}
-          <div class="govuk-checkboxes" data-module="govuk-checkboxes">
-            <div class="govuk-checkboxes__item">
-              <input class="govuk-checkboxes__input" id="f-checked-and-happy" name="checked-and-happy" type="checkbox" value="1">
-              <label class="govuk-label govuk-checkboxes__label" for="f-checked-and-happy">
-                {{ if .Lpa.CertificateProvider.CarryOutBy.IsPaper }}
-                  {{ trFormat .App "iveCheckedThisLpaAndImHappyToShowToCertificateProvider" "CertificateProviderFullName" .Lpa.CertificateProvider.FullName }}
-                {{ else }}
-                  {{ trFormat .App "iveCheckedThisLpaAndImHappyToShareWithCertificateProvider" "CertificateProviderFullName" .Lpa.CertificateProvider.FullName }}
-                {{ end }}
-              </label>
+      {{ if .CanContinue }}
+        <form novalidate method="post">
+          <div class="govuk-form-group  {{ if .Errors.Has "checked-and-happy" }}govuk-form-group--error{{ end }}">
+            {{ template "error-message" (errorMessage . "checked-and-happy") }}
+            <div class="govuk-checkboxes" data-module="govuk-checkboxes">
+              <div class="govuk-checkboxes__item">
+                <input class="govuk-checkboxes__input" id="f-checked-and-happy" name="checked-and-happy" type="checkbox" value="1">
+                <label class="govuk-label govuk-checkboxes__label" for="f-checked-and-happy">
+                  {{ if .Lpa.CertificateProvider.CarryOutBy.IsPaper }}
+                    {{ trFormat .App "iveCheckedThisLpaAndImHappyToShowToCertificateProvider" "CertificateProviderFullName" .Lpa.CertificateProvider.FullName }}
+                  {{ else }}
+                    {{ trFormat .App "iveCheckedThisLpaAndImHappyToShareWithCertificateProvider" "CertificateProviderFullName" .Lpa.CertificateProvider.FullName }}
+                  {{ end }}
+                </label>
+              </div>
             </div>
           </div>
-        </div>
 
-        {{ if .Completed }}
-          <div class="govuk-warning-text">
-            <span class="govuk-warning-text__icon" aria-hidden="true">!</span>
-            <strong class="govuk-warning-text__text">
-              <span class="govuk-warning-text__assistive"></span>
-              {{ tr .App "onceYouClickCertificateProviderWillBeSentText" }}
-            </strong>
-          </div>
-        {{ else }}
-          {{ template "details" (details . "whatHappensIfIChange" "whatHappensIfIChangeDetails" false) }}
-        {{ end }}
+          {{ if .Completed }}
+            <div class="govuk-warning-text">
+              <span class="govuk-warning-text__icon" aria-hidden="true">!</span>
+              <strong class="govuk-warning-text__text">
+                <span class="govuk-warning-text__assistive"></span>
+                {{ tr .App "onceYouClickCertificateProviderWillBeSentText" }}
+              </strong>
+            </div>
+          {{ else }}
+            {{ template "details" (details . "whatHappensIfIChange" "whatHappensIfIChangeDetails" false) }}
+          {{ end }}
 
-        <p class="govuk-body govuk-!-font-weight-bold">{{ tr .App "onceConfirmedNotAbleToMakeChanges" }}</p>
+          <p class="govuk-body govuk-!-font-weight-bold">{{ tr .App "onceConfirmedNotAbleToMakeChanges" }}</p>
 
-        {{ template "data-loss-warning-dialog" . }}
+          {{ template "data-loss-warning-dialog" . }}
 
-        <div class="govuk-button-group">
-          <button type="submit" id="save-and-continue-btn" class="govuk-button" data-module="govuk-button">{{ tr .App "confirm" }}</button>
-          <a href="{{ link .App (.App.Paths.TaskList.Format .App.LpaID) }}" id="return-to-tasklist-btn" class="govuk-button govuk-button--secondary">{{ tr .App "returnToTaskList" }}</a>
-        </div>
-        {{ template "csrf-field" . }}
-      </form>
+          <div class="govuk-button-group">
+            <button type="submit" id="save-and-continue-btn" class="govuk-button" data-module="govuk-button">{{ tr .App "confirm" }}</button>
+            <a href="{{ link .App (.App.Paths.TaskList.Format .App.LpaID) }}" id="return-to-tasklist-btn" class="govuk-button govuk-button--secondary">{{ tr .App "returnToTaskList" }}</a>
+          </div>
+          {{ template "csrf-field" . }}
+        </form>
+      {{ else }}
+        <a href="{{ link .App (.App.Paths.TaskList.Format .App.LpaID) }}" id="return-to-tasklist-btn" class="govuk-button">{{ tr .App "returnToTaskList" }}</a>
+      {{ end }}
     </div>
   </div>
 {{ end }}