---
title: Accounts Controllers
slug: extensions/accounts-controllers
docTags: 
createdAt: 2026-09-22T18:20:22.602Z
---

The cartridge extends SFRA's `Account`, `Address`, and `PaymentInstruments` controllers with `server.prepend`/`server.append`, and each extension calls the same `forterCustomerUpdate.js` pipelet used across every account event. Response actions for each outcome are configured on [Accounts Configuration](https://docs.forter.com/extensions/accounts-configuration).

## Extended routes

| Customer action                     | Route                              | Extension        | Forter call                       | On decline                                                             |
| ----------------------------------- | ---------------------------------- | ---------------- | --------------------------------- | ---------------------------------------------------------------------- |
| Log in                              | `Account-Login`                    | prepend          | login                             | Logs the customer back out and returns the decline message.            |
| Sign up                             | `Account-SubmitRegistration`       | prepend + append | signup                            | Deletes the account SFRA just created and returns the decline message. |
| Update profile (name, email, phone) | `Account-SaveProfile`              | prepend + append | profile-access (email/phone only) | Reverts the changed fields and returns the decline message.            |
| Change password                     | `Account-SavePassword`             | append           | profile-access                    | Reverts the password and returns the decline message.                  |
| Add or edit an address              | `Address-SaveAddress`              | prepend          | profile-access                    | Blocks the save and returns the decline message.                       |
| Remove an address                   | `Address-DeleteAddress`            | prepend          | profile-access                    | Blocks the delete (403) and returns the decline message.               |
| Add a payment method                | `PaymentInstruments-SavePayment`   | prepend          | profile-access                    | Blocks the save and returns the decline message.                       |
| Remove a payment method             | `PaymentInstruments-DeletePayment` | prepend          | profile-access                    | Blocks the delete (403) and returns the decline message.               |

Profile-access calls only fire for the fields that actually changed. Editing just a phone number doesn't trigger an email check, and saving a profile with no email/phone change doesn't call Forter at all.

On a `VERIFICATION_REQUIRED` decision, `Login` and every profile-access caller also report an authentication-result back to Forter (`forterAuthenticationAttemptUpdate.js`), tied to the decision's `correlationId`. See [Accounts Configuration](https://docs.forter.com/extensions/accounts-configuration) for what you still need to build to act on a Verification Required outcome.

## Signup declines reverse account creation

SFRA's base `SubmitRegistration` creates the customer account before Forter's appended handler runs. On decline, the handler removes that same account (`CustomerMgr.removeCustomer`) rather than preventing its creation up front. The customer briefly exists mid-request and is gone by the time the decline response is returned.

## Two different decline mechanisms

`SaveProfile` and `SavePassword` revert an already-committed change. Both use `server.append`, so SFRA's base logic runs and commits the new value first; Forter's handler captures the previous value going in and, on decline, restores it. `SavePassword`'s implementation says this directly: "Base commits the new password inside its own BeforeComplete... so if Forter declines we need the old password to roll the change back."

`SaveAddress`, `DeleteAddress`, and `DeletePayment` never let the change happen in the first place. All three are `server.prepend` hooks that check Forter before calling `next()`. On decline, the base save/delete logic never runs, so there's nothing to roll back.

Either way, the customer sees the decline message and their account ends up exactly as it was before the request.

## Which declines render automatically, and which need a frontend change

`Login`, `SubmitRegistration`, `SaveProfile`, `SavePassword`, `SaveAddress`, and `SavePayment` all return `{ success: false, error: [...] }`. Base SFRA's client-side `formValidation()` helper, already wired into each of these forms' AJAX success handlers, renders that `error` array as an alert banner automatically. No frontend change needed.

`DeleteAddress` also renders automatically, through a different path: its decline returns HTTP 403, which base SFRA's `addressBook.js` AJAX error handler catches and displays via `createErrorNotification(err.responseJSON.errorMessage)`.

`DeletePayment` is the exception. Its decline also returns HTTP 403 with `errorMessage`, but base SFRA's `paymentInstruments.js` error handler for removing a payment method only checks for a `redirectUrl` — there's no fallback branch that displays `errorMessage`. A Forter decline here fails silently unless you add one. Override `removePayment` in a cartridge ahead of `app_storefront_base` and add the missing branch:

```javascript
error: function (err) {
    if (err.responseJSON.redirectUrl) {
        window.location.href = err.responseJSON.redirectUrl;
    } else if (err.responseJSON.errorMessage) {
        // display err.responseJSON.errorMessage to the customer
    }
    $.spinner().stop();
}
```

## Payment methods: SavePayment fully replaces the base route

Unlike every other row in the table above, `PaymentInstruments-SavePayment`'s prepend never calls `next()`. It reimplements card validation and instrument creation itself and always terminates the route directly, so SFRA's base `SavePayment` logic never runs.

This matters for PSP cartridges that also take over payment-saving. If a PSP tokenizes cards through its own route, bypassing `PaymentInstruments-SavePayment` entirely, Forter's controller hook never fires. Call the `app.payment.saved` hook from the PSP's own flow instead:

```javascript
hooksHelper('app.payment.saved', 'paymentSaved', paymentData, ...);
```

See [Example: Stripe Payment Element](https://docs.forter.com/extensions/example-stripe-payment-element) for this applied in detail, and [PSP Integration Guide](https://docs.forter.com/extensions/psp-integration-guide) for the equivalent pattern on the order side (`app.post.auth`).

If a PSP cartridge instead prepends `PaymentInstruments-SavePayment` directly rather than using its own route, both prepends compete for the same unconditional termination. Only the one earlier in the cartridge path runs, and it's silent either way. Placing `int_forter_sfra` before any PSP cartridge (see [Import Cartridge](https://docs.forter.com/extensions/import-cartridge)) decides that order, but doesn't eliminate the risk; if the PSP cartridge wins, add a call to the `app.payment.saved` hook from its flow so Forter still sees the update.
