Skip to content

Commit

Permalink
PP-11212 Improve go-live and test account journeys
Browse files Browse the repository at this point in the history
Replace the 3 buttons on the services page to create live and test
gateway accounts for Stripe/Non-Stripe with just 2 buttons:
- Go live
- Add test account

When "Go live" is clicked, determine the PSP from the go-live stage
for the service. If the service hasn't completed a go-live request,
show an error.

When "Add a test account" is clicked, show a new page with radio
buttons to select the PSP for the test account (Stripe/Worldpay/Sandbox)

On the page that asks for details about the service/gateway account
before creating the gateway account in Pay:

- remove the radio button for Live/Not live, as we already know the
answer now
- remove the radio buttons for selecting the PSP, as we already know
the answer now
- prefill the description input, but still allow this to be edited
- hide the "Stripe credentials" section if we are not adding a Stripe
account.
  • Loading branch information
stephencdaly committed Jan 11, 2024
1 parent db82588 commit f94a898
Show file tree
Hide file tree
Showing 21 changed files with 269 additions and 122 deletions.
4 changes: 4 additions & 0 deletions src/lib/liveStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const liveStatus = {
live: 'live',
notLive: 'not-live'
}
1 change: 1 addition & 0 deletions src/lib/pay-request/services/admin_users/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export enum GoLiveStage {
EnteredOrganisationAddress = 'ENTERED_ORGANISATION_ADDRESS',
ChosenPSPStripe = 'CHOSEN_PSP_STRIPE',
TermsAgreedStripe = 'TERMS_AGREED_STRIPE',
TermsAgreedWorldpay = 'TERMS_AGREED_GOV_BANKING_WORLDPAY',
Denied = 'DENIED',
Live = 'LIVE'
}
Expand Down
7 changes: 7 additions & 0 deletions src/lib/providers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const providers = {
sandbox: 'sandbox',
worldpay: 'worldpay',
smartpay: 'smartpay',
epdq: 'epdq',
stripe: 'stripe'
}
55 changes: 12 additions & 43 deletions src/web/modules/gateway_accounts/create.njk
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,20 @@
{% extends "layout/layout.njk" %}

{% block main %}
<h1 class="govuk-heading-m">Create new gateway account</h1>
{% set liveOrTest %}{% if live === 'live' %}live{% else%}test{% endif%}{% endset %}

{% if live === 'live' %}
<span class="govuk-caption-m">Go live</span>
{% else %}
<span class="govuk-caption-m">Add test account</span>
{% endif %}
<h1 class="govuk-heading-m">Create {{ liveOrTest }} {{ provider | capitalize }} gateway account</h1>

{% if errors %}
{{ errorSummary({ errors: errors }) }}
{% endif %}

<form method="post" action="/gateway_accounts/create/confirm">
<!-- @TODO(sfount) consider using standard markup vs. macros -->
<!-- @TODO(sfount) repopulating values in this method is very manual -->
{{ govukRadios({
classes: "govuk-radios--inline",
idPrefix: "live",
name: "live",
fieldset: {
legend: {
text: "Live status",
isPageHeading: false
}
},
items: [
{ value: "live", text: "Live", checked: recovered.live and recovered.live === "live" or true },
{ value: "not-live", text: "Not live", checked: recovered.live === "not-live" }
]
})
}}

{{ govukRadios({
classes: "govuk-radios--inline",
idPrefix: "provider",
name: "provider",
fieldset: {
legend: {
text: "Card provider",
isPageHeading: false
}
},
items: [
{ value: "card-sandbox", text: "Sandbox", checked: recovered.provider and recovered.provider === "card-sandbox" or true },
{ value: "worldpay", text: "Worldpay", checked: recovered.provider === "worldpay" },
{ value: "stripe", text: "Stripe", checked: recovered.provider === "stripe" }
]
})
}}

{% if service %}
<div class="govuk-form-group">
Expand Down Expand Up @@ -77,24 +48,20 @@
hint: { html: '<p>GOV.UK Pay standard: "${Department} ${Service} ${Provider} ${IsLive}"</p><p>Blue Badge standard: "${Service} Blue Badge admin Stripe LIVE"</p>' },
id: "description",
name: "description",
value: recovered.description,
value: description or recovered.description,
errorMessage: errorMap.description and { text: errorMap.description },
autocomplete: "off"
}) }}

{% if linkedCredentials %}
<div class="govuk-form-group">
<label class="govuk-label">Stripe credentials</label>
<div class="govuk-hint">
This is only required for Stripe accounts
</div>
<input readonly class="govuk-input" id="credentials" name="credentials" type="text" value="{{ linkedCredentials }}" autocomplete="off">
<input hidden id="systemLinkedCredentials" name="systemLinkedCredentials" value="{{ linkedCredentials }}">
</div>
{% else %}
{% elif provider === "stripe" %}
{{govukInput({
label: { text: "Stripe credentials" },
hint: { text: "This is only required for Stripe accounts" },
id: "credentials",
name: "credentials",
value: recovered.credentials,
Expand Down Expand Up @@ -171,6 +138,8 @@
})
}}

<input type="hidden" name="live" value="{{ live or recovered.live }}">
<input type="hidden" name="provider" value="{{ provider or recovered.provider }}">
<input type="hidden" name="_csrf" value="{{ csrf }}">
</form>
{% endblock %}
6 changes: 0 additions & 6 deletions src/web/modules/gateway_accounts/detail.njk
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,6 @@
{% endif %}
</dd>
</div>
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key"><span class="govuk-caption-m">Payment method</span></dt>
<dd class="govuk-summary-list__value">
{% if account.payment_method %} {{ account.payment_method }} {% else %} CARD {% endif %}
</dd>
</div>
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key"><span class="govuk-caption-m">Service</span></dt>
<dd class="govuk-summary-list__value">{{ account.service_name }}</dd>
Expand Down
27 changes: 5 additions & 22 deletions src/web/modules/gateway_accounts/gatewayAccount.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,8 @@ import Validated from '../common/validated'
import {ValidationError} from '../../../lib/errors'
import {CreateGatewayAccountRequest} from '../../../lib/pay-request/services/connector/types'
import {AccountType} from '../../../lib/pay-request/shared'

const liveStatus = {
live: 'live',
notLive: 'not-live'
}

const sandbox = {
card: 'card-sandbox'
}

const cardProviders = {
sandbox: sandbox.card,
worldpay: 'worldpay',
smartpay: 'smartpay',
epdq: 'epdq',
stripe: 'stripe'
}
import {liveStatus} from "../../../lib/liveStatus";
import {providers} from "../../../lib/providers";

class GatewayAccount extends Validated {
@IsIn(Object.values(liveStatus))
Expand All @@ -39,7 +24,7 @@ class GatewayAccount extends Validated {
@IsNotEmpty({message: 'Please enter a service name'})
public serviceName: string;

@IsIn([...Object.values(cardProviders)])
@IsIn([...Object.values(providers)])
@IsString()
@IsNotEmpty()
public provider: string;
Expand All @@ -57,11 +42,11 @@ class GatewayAccount extends Validated {
public validate(): void {
super.validate()

if (this.live === liveStatus.live && Object.values(sandbox).includes(this.provider)) {
if (this.isLive() && this.provider === providers.sandbox) {
throw new ValidationError('Live accounts cannot have Sandbox provider')
}

if (this.provider === cardProviders.stripe && !this.credentials.trim()) {
if (this.provider === providers.stripe && !this.credentials.trim()) {
throw new ValidationError('Stripe credentials are required')
}
}
Expand All @@ -80,8 +65,6 @@ class GatewayAccount extends Validated {

// formats gateway account according to the Connector patch standard
public formatPayload(): CreateGatewayAccountRequest {
if (Object.values(sandbox).includes(this.provider)) this.provider = 'sandbox'

const payload: CreateGatewayAccountRequest = {
payment_provider: this.provider,
description: this.description,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import { formatErrorsForTemplate } from '../common/validationErrorFormat'
const buildPreservedQuery = function buildPreservedQuery(body: { [key: string]: string }): string {
const supported: { [key: string]: string } = {
systemLinkedService: 'service',
systemLinkedCredentials: 'credentials'
systemLinkedCredentials: 'credentials',
provider: 'provider',
live: 'live'
}

const queryElements: string[] = []
Expand Down
24 changes: 21 additions & 3 deletions src/web/modules/gateway_accounts/gateway_accounts.http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import {NextFunction, Request, Response} from 'express'
import {stringify} from 'qs'
import moment from 'moment'
import {capitalize} from "lodash"

import logger from '../../../lib/logger'

Expand Down Expand Up @@ -73,16 +74,28 @@ async function listCSV(req: Request, res: Response): Promise<void> {

async function create(req: Request, res: Response): Promise<void> {
const serviceId = req.query.service as string
const live = req.query.live as string
const provider = req.query.provider as string

if (!live || !provider) {
throw new Error('Expected "live" and "provider" query parameters')
}

const context: {
linkedCredentials: string;
live: string;
provider: string;
recovered?: object;
service?: Service;
description?: string;
flash: object;
errors?: ClientFormError[];
errorMap?: object;
csrf: string;
} = {
linkedCredentials: req.query.credentials as string,
live,
provider,
flash: req.flash(),
csrf: req.csrfToken()
}
Expand All @@ -107,13 +120,18 @@ async function create(req: Request, res: Response): Promise<void> {
if (serviceId) {
const service = await AdminUsers.services.retrieve(serviceId)
context.service = service
context.description = `${service.merchant_details.name} ${service.name} ${capitalize(provider)} ${live === 'live' && 'LIVE' || 'TEST'}`
}
res.render('gateway_accounts/create', context)
}

async function confirm(req: Request, res: Response): Promise<void> {
const account = new GatewayAccountFormModel(req.body)
res.render('gateway_accounts/confirm', {account, request: req.body, csrf: req.csrfToken()})
async function confirm(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const account = new GatewayAccountFormModel(req.body)
res.render('gateway_accounts/confirm', {account, request: req.body, csrf: req.csrfToken()})
} catch (error) {
next(error)
}
}

function getGoLiveUrlForServiceUsingWorldpay(serviceId: string) {
Expand Down
2 changes: 1 addition & 1 deletion src/web/modules/gateway_accounts/gateway_accounts.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ describe('Gateway Accounts', () => {
expect(() => {
const details = _.cloneDeep(validGatewayAccountDetails)
details.live = 'live'
details.provider = 'card-sandbox'
details.provider = 'sandbox'

// eslint-disable-next-line no-new
new GatewayAccount(details)
Expand Down
15 changes: 15 additions & 0 deletions src/web/modules/services/AddTestAccountForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {IsBoolean, IsEmail, IsNotEmpty, IsString} from 'class-validator'

import Validated from '../common/validated'

export default class AddTestAccountFormRequest extends Validated {
@IsNotEmpty()
@IsString()
public provider: string;

public constructor(formValues: {[key: string]: string}) {
super()
this.provider = formValues.provider
this.validate()
}
}
Loading

0 comments on commit f94a898

Please sign in to comment.