Solaris.ChangeRequests (Solaris v1.0.0)

Copy Markdown View Source

Strong Customer Authentication (SCA) / Change Request completion flow.

Many Solaris write operations (SEPA transfers, person updates, adding trusted IBANs, etc.) require customer authentication. When triggered, the API returns 202 Accepted with a change_request_id in the body. The customer must authorise the action before it is executed.

Authentication Methods

SMS OTP — Sends a one-time password to the customer's verified mobile number:

POST /change_requests/{id}/authorize/otp   # send SMS
POST /change_requests/{id}/confirm/otp     # submit code

Device Signing — Uses the Solaris mobile SDK on the customer's device:

GET  /change_requests/{id}/challenge       # get challenge string
POST /change_requests/{id}/confirm/device  # submit signed token

Full Flow

# 1. Initiate any action (e.g. SEPA transfer)
{:ok, result} = Solaris.Banking.SEPA.create_person_credit_transfer(...)
change_request_id = result["change_request_id"]

# 2a. SMS OTP path
{:ok, _} = Solaris.ChangeRequests.authorize_with_sms(change_request_id)
# ... customer receives SMS and submits OTP ...
{:ok, _} = Solaris.ChangeRequests.confirm_with_otp(change_request_id, "483920")

# 2b. Device signing path
{:ok, challenge} = Solaris.ChangeRequests.get_device_challenge(change_request_id)
# ... pass challenge["challenge"] to mobile SDK, get signed_token back ...
{:ok, _} = Solaris.ChangeRequests.confirm_with_device(change_request_id, signed_token)

Statuses

StatusDescription
AUTHORIZATION_REQUIREDAwaiting customer auth
COMPLETEDAuthorised and executed
FAILEDAuthentication failed
EXPIREDChallenge window expired

Summary

Functions

Initiates an SMS OTP challenge for a change request.

Confirms a change request using a device-signed token.

Confirms a change request using the SMS OTP code.

Retrieves a change request by ID.

Retrieves the device signing challenge string for a change request.

Lists change requests for a business.

Lists change requests for a person.

Polls a change request until it reaches a terminal status.

Functions

authorize_with_sms(change_request_id, opts \\ [])

@spec authorize_with_sms(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, Solaris.Error.t()}

Initiates an SMS OTP challenge for a change request.

Sends a one-time password to the customer's registered and verified mobile number. The number must have been confirmed via Solaris.Onboarding.Persons.confirm_mobile_number/3 before use.

confirm_with_device(change_request_id, signed_token, opts \\ [])

@spec confirm_with_device(String.t(), String.t(), keyword()) ::
  {:ok, map()} | {:error, Solaris.Error.t()}

Confirms a change request using a device-signed token.

The signed_token is produced by the Solaris mobile SDK after the customer authenticates on their enrolled device.

confirm_with_otp(change_request_id, otp, opts \\ [])

@spec confirm_with_otp(String.t(), String.t(), keyword()) ::
  {:ok, map()} | {:error, Solaris.Error.t()}

Confirms a change request using the SMS OTP code.

Returns {:error, %Error{code: :forbidden}} if the OTP is invalid or expired.

Examples

{:ok, _} = Solaris.ChangeRequests.confirm_with_otp("cchr_123", "483920")

get(change_request_id, opts \\ [])

@spec get(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, Solaris.Error.t()}

Retrieves a change request by ID.

Examples

{:ok, cr} = Solaris.ChangeRequests.get("cchr_123")
cr["status"]  # => "AUTHORIZATION_REQUIRED"

get_device_challenge(change_request_id, opts \\ [])

@spec get_device_challenge(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, Solaris.Error.t()}

Retrieves the device signing challenge string for a change request.

Pass the returned challenge["challenge"] value to the Solaris mobile SDK for signing on the customer's enrolled device.

list_for_business(business_id, opts \\ [])

@spec list_for_business(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, Solaris.Error.t()}

Lists change requests for a business.

list_for_person(person_id, opts \\ [])

@spec list_for_person(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, Solaris.Error.t()}

Lists change requests for a person.

poll_until_complete(change_request_id, opts \\ [])

@spec poll_until_complete(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, Solaris.Error.t() | :timeout}

Polls a change request until it reaches a terminal status.

Useful for synchronous test flows. Not recommended in production — prefer listening to the relevant webhook instead.

Options

OptionDefaultDescription
:interval_ms2_000Polling interval in milliseconds
:max_attempts15Maximum number of poll attempts
:terminal_statuses["COMPLETED", "FAILED", "EXPIRED"]Stop conditions

Examples

{:ok, completed} = Solaris.ChangeRequests.poll_until_complete("cchr_123")
completed["status"]  # => "COMPLETED"

# Returns {:error, :timeout} if never completed within max_attempts