001/*
002 *   Copyright 2024 Vonage
003 *
004 *   Licensed under the Apache License, Version 2.0 (the "License");
005 *   you may not use this file except in compliance with the License.
006 *   You may obtain a copy of the License at
007 *
008 *        http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *   Unless required by applicable law or agreed to in writing, software
011 *   distributed under the License is distributed on an "AS IS" BASIS,
012 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *   See the License for the specific language governing permissions and
014 *   limitations under the License.
015 */
016package com.vonage.client.verify2;
017
018import com.fasterxml.jackson.annotation.*;
019import com.vonage.client.Jsonable;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Locale;
023import java.util.Objects;
024import java.util.regex.Pattern;
025
026/**
027 * Defines properties for a verify request to be sent to the user. Such properties include the brand
028 * (i.e. the sender that the user sees asking them for verification), language to send the message in,
029 * length of the verification code and timeout between delivery channel workflows.
030 * <p>
031 * A verification request can have multiple "workflows". Each workflow defines a contact
032 * method for verification. The order of workflows defines the order in which each contact
033 * method will be attempted. Once a contact method has succeeded, the remaining workflows will
034 * be aborted. This flexibility exists as a fallback / backup verification. A different communication
035 * channel and/or number can be contacted if desired.
036 */
037public class VerificationRequest implements Jsonable {
038        static final Pattern CODE_REGEX = Pattern.compile("[a-zA-Z0-9]{4,10}");
039
040        @JsonProperty("locale") protected final Locale locale;
041        protected final Integer channelTimeout, codeLength;
042        protected final Boolean fraudCheck;
043        protected final String brand, code, clientRef;
044        protected final List<Workflow> workflows;
045
046        VerificationRequest(Builder builder) {
047                locale = builder.locale;
048                clientRef = builder.clientRef;
049                fraudCheck = builder.fraudCheck != null && !builder.fraudCheck ? false : null;
050                if ((brand = builder.brand) == null || brand.trim().isEmpty()) {
051                        throw new IllegalArgumentException("Brand name is required.");
052                }
053                if (brand.length() > 16) {
054                        throw new IllegalArgumentException("Brand cannot exceed 16 characters in length.");
055                }
056                if ((channelTimeout = builder.timeout) != null && (channelTimeout < 15 || channelTimeout > 900)) {
057                        throw new IllegalArgumentException("Delivery wait timeout must be between 15 and 900 seconds.");
058                }
059                if ((workflows = builder.workflows).isEmpty()) {
060                        throw new IllegalStateException("At least one workflow must be defined.");
061                }
062                if ((codeLength = builder.codeLength) != null && (codeLength < 4 || codeLength > 10)) {
063                        throw new IllegalArgumentException("Code length must be between 4 and 10 (inclusive).");
064                }
065                if ((code = builder.code) != null && !CODE_REGEX.matcher(code).matches()) {
066                        throw new IllegalArgumentException("Custom verification code must be 4-10 alphanumeric characters.");
067                }
068                if (isCodeless()) {
069                        if (codeLength != null) {
070                                throw new IllegalStateException("Code length has no effect for codeless workflows.");
071                        }
072                        if (code != null) {
073                                throw new IllegalStateException("Code has no effect for codeless workflows.");
074                        }
075                }
076                if (code != null && codeLength != null && code.length() != codeLength) {
077                        throw new IllegalStateException("Code '"+code+"' is not "+codeLength+" characters.");
078                }
079                if (workflows.stream().anyMatch(SilentAuthWorkflow.class::isInstance)) {
080                        if (!(workflows.get(0) instanceof SilentAuthWorkflow)) {
081                                throw new IllegalStateException("Silent Auth must be the first workflow.");
082                        }
083                }
084        }
085
086        /**
087         * The brand that is sending the verification request. This is what
088         * the user will see when they receive the notification.
089         *
090         * @return The verification sender name.
091         */
092        @JsonProperty("brand")
093        public String getBrand() {
094                return brand;
095        }
096
097        /**
098         * Language for the request in ISO_639-1 format.
099         *
100         * @return The locale, or {@code null} if not set (the default).
101         */
102        @JsonIgnore
103        public Locale getLocale() {
104                return locale;
105        }
106
107        @JsonGetter("locale")
108        protected String getLocaleAsString() {
109                return locale == null ? null : locale.toString().replace("_", "-").toLowerCase();
110        }
111
112        /**
113         * Specifies the wait time in seconds between attempts to delivery the verification code.
114         *
115         * @return The delivery timeout, or {@code null} if not set (the default).
116         */
117        @JsonProperty("channel_timeout")
118        public Integer getChannelTimeout() {
119                return channelTimeout;
120        }
121
122        /**
123         * Length of the code to send to the user. Does not apply to codeless verification channels.
124         *
125         * @return The verification code length, or {@code null} if unset (the default) or not applicable.
126         */
127        @JsonProperty("code_length")
128        public Integer getCodeLength() {
129                return codeLength;
130        }
131
132        /**
133         * Custom alphanumeric verification code to send to the user instead of the Vonage-generated one.
134         *
135         * @return The custom code, or {@code null} if unset.
136         */
137        @JsonProperty("code")
138        public String getCode() {
139                return code;
140        }
141
142        /**
143         * If the client_ref is set when the request is sent, it will be included in the callbacks.
144         *
145         * @return The client reference, or {@code null} if not set.
146         */
147        @JsonProperty("client_ref")
148        public String getClientRef() {
149                return clientRef;
150        }
151
152        /**
153         * Determines if the network level fraud check is enforced. See
154         * <a href=https://developer.vonage.com/en/verify/verify-v2/guides/v2-anti-fraud>the documentation</a>.
155         * This feature only takes effect if it has been enabled on your account.
156         *
157         * @return Whether network block is respected, or {@code null} if not set or {@code true} (the default).
158         */
159        @JsonProperty("fraud_check")
160        public Boolean getFraudCheck() {
161                return fraudCheck;
162        }
163
164        /**
165         * Workflows are a sequence of actions that Vonage use to reach the user you wish to verify with a PIN code.
166         *
167         * @return The list of workflows (contact methods) to be used in verification, in order of preference.
168         */
169        @JsonProperty("workflow")
170        protected List<Workflow> getWorkflows() {
171                return workflows;
172        }
173
174        /**
175         * Determines if the workflows defined in this request do not prompt the user for code entry.
176         *
177         * @return {@code true} if all the defined workflows do not require a code or {@code false}
178         * if at least one of the contact methods involves a code being sent to the user.
179         */
180        @JsonIgnore
181        public boolean isCodeless() {
182                return workflows.stream().allMatch(type ->
183                                type instanceof WhatsappCodelessWorkflow ||
184                                type instanceof SilentAuthWorkflow
185                );
186        }
187
188        /**
189         * Entry point for constructing an instance of this class.
190         *
191         * @return A new Builder.
192         */
193        public static Builder builder() {
194                return new Builder();
195        }
196        
197        public static final class Builder {
198                Boolean fraudCheck;
199                String brand, code, clientRef;
200                Integer timeout, codeLength;
201                Locale locale;
202                List<Workflow> workflows = new ArrayList<>(1);
203
204                private Builder() {}
205
206                /**
207                 * (REQUIRED)
208                 * Workflows are a sequence of actions that Vonage use to reach the user you wish to verify with a PIN code.
209                 * Each workflow represents a contact method - typically, the channel and number. A verification request must
210                 * define at least one workflow. The order in which they are defined is the order in which they will be
211                 * attempted, until one is successful. The first one you add will be the preferred contact method. Any
212                 * subsequent ones act as a fallback / backup.
213                 *
214                 * @param workflow The workflow to add to the list.
215                 *
216                 * @return This builder.
217                 */
218                public Builder addWorkflow(Workflow workflow) {
219                        workflows.add(Objects.requireNonNull(workflow, "Workflow cannot be null"));
220                        return this;
221                }
222
223                /**
224                 * (REQUIRED if {@linkplain #addWorkflow(Workflow)}) has not been called.
225                 * Workflows are a sequence of actions that Vonage use to reach the user you wish to verify with a PIN code.
226                 * Each workflow represents a contact method - typically, the channel and number. A verification request must
227                 * define at least one workflow. The order in which they are defined is the order in which they will be
228                 * attempted, until one is successful. The first one you add will be the preferred contact method. Any
229                 * subsequent ones act as a fallback / backup.
230                 *
231                 * @param workflows The workflows to use.This will replace the existing list.
232                 *
233                 * @return This builder.
234                 */
235                public Builder workflows(List<Workflow> workflows) {
236                        this.workflows.clear();
237                        this.workflows.addAll(Objects.requireNonNull(workflows, "Workflows must not be null."));
238                        return this;
239                }
240
241                /**
242                 * (REQUIRED)
243                 * The brand that is sending the verification request.
244                 * This is what the user will see when they receive the notification.
245                 *
246                 * @param brand The brand name.
247                 *
248                 * @return This builder.
249                 */
250                public Builder brand(String brand) {
251                        this.brand = brand;
252                        return this;
253                }
254
255                /**
256                 * (OPTIONAL)
257                 * An optional alphanumeric custom code to use, if you don't want Vonage to generate the code. Must be
258                 * between 4 and 10 alphanumeric characters. This is not used for Silent Auth or Whatsapp Interactive.
259                 *
260                 * @param code The code to use as a string.
261                 *
262                 * @return This builder.
263                 */
264                public Builder code(String code) {
265                        this.code = code;
266                        return this;
267                }
268
269                /**
270                 * (OPTIONAL)
271                 * Length of the code to send to the user, must be between 4 and 10 (inclusive).
272                 * This is not used for Silent Auth or Whatsapp Interactive.
273                 *
274                 * @param codeLength The verification code length.
275                 *
276                 * @return This builder.
277                 */
278                public Builder codeLength(int codeLength) {
279                        this.codeLength = codeLength;
280                        return this;
281                }
282
283                /**
284                 * (OPTIONAL)
285                 * Specifies the wait time in seconds between attempts to delivery the verification code
286                 * between workflows. Must be between 60 and 900. Default is 300.
287                 *
288                 * @param timeout The delivery timeout in seconds.
289                 *
290                 * @return This builder.
291                 */
292                public Builder channelTimeout(int timeout) {
293                        this.timeout = timeout;
294                        return this;
295                }
296
297                /**
298                 * (OPTIONAL)
299                 * Set the language that this request will be delivered in. Refer to
300                 * <a href=https://developer.vonage.com/en/verify/guides/verify-v2-languages>the documentation</a>
301                 * for a list of supported locales.
302                 *
303                 * @param locale The language locale.
304                 *
305                 * @return This builder.
306                 *
307                 * @since 8.0.0
308                 */
309                public Builder locale(Locale locale) {
310                        if (locale == null || locale.toString().isEmpty()) {
311                                throw new IllegalArgumentException("Invalid locale");
312                        }
313                        this.locale = locale;
314                        return this;
315                }
316
317                /**
318                 * (OPTIONAL)
319                 * Set the language that this request will be delivered in.
320                 *
321                 * @param locale The language locale as a string. This should be a
322                 * <a href=https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes>ISO 639-1 code</a>.
323                 *
324                 * @return This builder.
325                 *
326                 * @since 8.0.0
327                 * @see #locale(Locale)
328                 */
329                public Builder locale(String locale) {
330                        return locale(Locale.forLanguageTag(locale));
331                }
332
333                /**
334                 * (OPTIONAL)
335                 * If this reference is set when the request is sent, it will be included in the callbacks.
336                 *
337                 * @param clientRef The callback reference for this request.
338                 *
339                 * @return This builder.
340                 */
341                public Builder clientRef(String clientRef) {
342                        this.clientRef = clientRef;
343                        return this;
344                }
345
346                /**
347                 * (OPTIONAL)
348                 * Set this parameter to {@code false} to force through the request even if it's
349                 * blocked by the network's fraud protection. Refer to
350                 * <a href=https://developer.vonage.com/en/verify/verify-v2/guides/v2-anti-fraud>
351                 * the documentation</a> for details. This feature must be enabled on your account to take effect.
352                 *
353                 * @param fraudCheck Whether to enforce network block. Default is {@code true}.
354                 * Set to {@code false} to bypass a network block for this request.
355                 *
356                 * @return This builder.
357                 */
358                public Builder fraudCheck(boolean fraudCheck) {
359                        this.fraudCheck = fraudCheck;
360                        return this;
361                }
362
363                /**
364                 * Bypasses the network block used for fraud check. See {@link #fraudCheck(boolean)}.
365                 * 
366                 * @return This builder.
367                 * @see #fraudCheck(boolean)
368                 */
369                public Builder fraudCheck() {
370                        return fraudCheck(false);
371                }
372
373                /**
374                 * Constructs a VerificationRequest with this builder's properties.
375                 *
376                 * @return A new VerificationRequest instance.
377                 */
378                public VerificationRequest build() {
379                        return new VerificationRequest(this);
380                }
381        }
382}