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.vonage.client.DynamicEndpoint;
019import com.vonage.client.HttpWrapper;
020import com.vonage.client.RestEndpoint;
021import com.vonage.client.auth.JWTAuthMethod;
022import com.vonage.client.auth.ApiKeyHeaderAuthMethod;
023import com.vonage.client.common.HttpMethod;
024import java.util.List;
025import java.util.Objects;
026import java.util.UUID;
027import java.util.function.Function;
028
029public class Verify2Client {
030        final boolean hasJwtAuthMethod;
031        final RestEndpoint<VerificationRequest, VerificationResponse> verifyUser;
032        final RestEndpoint<VerifyCodeRequestWrapper, VerifyCodeResponse> verifyRequest;
033        final RestEndpoint<UUID, Void> cancel, nextWorkflow, deleteTemplate;
034        final RestEndpoint<ListTemplatesRequest, ListTemplatesResponse> listTemplates;
035        final RestEndpoint<UUID, Template> getTemplate;
036        final RestEndpoint<Template, Template> createTemplate, updateTemplate;
037        final RestEndpoint<ListTemplatesRequest, ListTemplateFragmentsResponse> listFragments;
038        final RestEndpoint<TemplateFragmentRequestWrapper, TemplateFragment> getFragment;
039        final RestEndpoint<TemplateFragmentRequestWrapper, Void> deleteFragment;
040        final RestEndpoint<TemplateFragment, TemplateFragment> createFragment, updateFragment;
041
042        /**
043         * Create a new Verify2Client.
044         *
045         * @param wrapper Http Wrapper used to create verification requests.
046         */
047        public Verify2Client(HttpWrapper wrapper) {
048                hasJwtAuthMethod = wrapper.getAuthCollection().hasAuthMethod(JWTAuthMethod.class);
049
050                @SuppressWarnings("unchecked")
051                final class Endpoint<T, R> extends DynamicEndpoint<T, R> {
052                        Endpoint(Function<T, String> pathGetter, HttpMethod method, R... type) {
053                                super(DynamicEndpoint.<T, R> builder(type)
054                                                .responseExceptionType(VerifyResponseException.class)
055                                                .wrapper(wrapper).requestMethod(method)
056                                                .authMethod(JWTAuthMethod.class, ApiKeyHeaderAuthMethod.class)
057                                                .pathGetter((de, req) -> {
058                                                        String base = de.getHttpWrapper().getHttpConfig().getApiBaseUri() + "/v2/verify";
059                                                        return pathGetter != null ? base + "/" + pathGetter.apply(req) : base;
060                                                })
061                                );
062                        }
063                }
064
065                verifyUser = new Endpoint<>(null, HttpMethod.POST);
066                verifyRequest = new Endpoint<>(req -> req.requestId, HttpMethod.POST);
067                cancel = new Endpoint<>(UUID::toString, HttpMethod.DELETE);
068                nextWorkflow = new Endpoint<>(id -> id + "/next-workflow", HttpMethod.POST);
069
070                final String templatesBase = "templates";
071                listTemplates = new Endpoint<>(__ -> templatesBase, HttpMethod.GET);
072                getTemplate = new Endpoint<>(id -> templatesBase+'/'+id, HttpMethod.GET);
073                createTemplate = new Endpoint<>(__ -> templatesBase, HttpMethod.POST);
074                updateTemplate = new Endpoint<>(req -> templatesBase+'/'+req.id, HttpMethod.PATCH);
075                deleteTemplate = new Endpoint<>(id -> templatesBase+'/'+id, HttpMethod.DELETE);
076
077                final String fragmentsBase = "/template_fragments";
078                listFragments = new Endpoint<>(req -> templatesBase+'/'+req.templateId + fragmentsBase, HttpMethod.GET);
079                getFragment = new Endpoint<>(req ->
080                                templatesBase+'/'+req.templateId + fragmentsBase+'/'+req.fragmentId, HttpMethod.GET
081                );
082                createFragment = new Endpoint<>(req -> templatesBase+'/'+req.templateId + fragmentsBase, HttpMethod.POST);
083                updateFragment = new Endpoint<>(req ->
084                                templatesBase+'/'+req.getTemplateId() + fragmentsBase+'/'+req.fragmentId, HttpMethod.PATCH
085                );
086                deleteFragment = new Endpoint<>(req ->
087                                templatesBase+'/'+req.templateId + fragmentsBase+'/'+req.fragmentId, HttpMethod.DELETE
088                );
089        }
090
091        private UUID validateId(String name, UUID id) {
092                return Objects.requireNonNull(id, name + " ID is required.");
093        }
094
095        private UUID validateRequestId(UUID requestId) {
096                return validateId("Request", requestId);
097        }
098
099        private UUID validateTemplateId(UUID templateId) {
100                return validateId("Template", templateId);
101        }
102
103        private UUID validateFragmentId(UUID fragmentId) {
104                return validateId("Fragment", fragmentId);
105        }
106
107        /**
108         * Request a verification be sent to a user. This is the first step in the verification process.
109         *
110         * @param request Properties of the verification request. You must specify the brand name and at least one
111         * contact method (workflow). For example, to verify using Whatsapp and fall back to a voice call as backup
112         * to the same number with a 6-digit code and a 3-minute wait between attempts:
113         * <pre>
114         * {@code VerificationRequest.builder()
115         *      .brand("My Company")
116         *      .addWorkflow(new WhatsappWorkflow("447000000001"))
117         *      .addWorkflow(new VoiceWorkflow("447000000001"))
118         *      .codeLength(6)
119         *      .channelTimeout(180)
120         *      .build()}.
121         * </pre>
122         *
123         * @return The server's response, if successful.
124         *
125         * @throws VerifyResponseException If the request was unsuccessful. This could be for the following reasons:
126         * <ul>
127         *     <li><b>409</b>: Concurrent verifications to the same number are not allowed.</li>
128         *     <li><b>422</b>: The value of one or more parameters is invalid.</li>
129         *     <li><b>429</b>: Rate limit hit. Please wait and try again.</li>
130         *     <li><b>500</b>: An error occurred on the Vonage platform.</li>
131         * </ul>
132         */
133        public VerificationResponse sendVerification(VerificationRequest request) {
134                if (request.isCodeless() && !hasJwtAuthMethod) {
135                        throw new IllegalStateException(
136                                "Codeless verification requires an application ID to be set in order to use webhooks."
137                        );
138                }
139                return verifyUser.execute(Objects.requireNonNull(request));
140        }
141
142        /**
143         * Check a supplied code against an existing verification request. If the code is valid,
144         * this method will return normally. Otherwise, a {@link VerifyResponseException} will be thrown.
145         *
146         * @param requestId ID of the verify request, obtained from {@link VerificationResponse#getRequestId()}.
147         * @param code The code supplied by the user.
148         *
149         * @return Details of the verification request (if the code matched).
150         *
151         * @throws VerifyResponseException If the code could not be verified. This could be for the following reasons:
152         * <ul>
153         *     <li><b>400</b>: The provided code does not match the expected value.</li>
154         *     <li><b>401</b>: Invalid credentials.</li>
155         *     <li><b>402</b>: Low balance.</li>
156         *     <li><b>404</b>: Request ID was not found or it has been verified already.</li>
157         *     <li><b>409</b>: The current workflow step does not support a code.</li>
158         *     <li><b>410</b>: An incorrect code has been provided too many times.</li>
159         *     <li><b>429</b>: Rate limit hit. Please wait and try again.</li>
160         *     <li><b>500</b>: An error occurred on the Vonage platform.</li>
161         * </ul>
162         */
163        public VerifyCodeResponse checkVerificationCode(UUID requestId, String code) {
164                return verifyRequest.execute(new VerifyCodeRequestWrapper(
165                                validateRequestId(requestId).toString(),
166                                Objects.requireNonNull(code, "Code is required.")
167                ));
168        }
169
170        /**
171         * Attempts to abort an active verification workflow.
172         * If successful (HTTP status 204), this method will return normally.
173         * Otherwise, a {@link VerifyResponseException} exception will be thrown, indicating a 404 response.
174         *
175         * @param requestId ID of the verify request, obtained from {@link VerificationResponse#getRequestId()}.
176         *
177         * @throws VerifyResponseException If the request could not be cancelled. This could be for the following reasons:
178         * <ul>
179         *     <li><b>401</b>: Invalid credentials.</li>
180         *     <li><b>402</b>: Low balance.</li>
181         *     <li><b>404</b>: Request ID was not found or it has been verified / cancelled already.</li>
182         *     <li><b>500</b>: An error occurred on the Vonage platform.</li>
183         * </ul>
184         */
185        public void cancelVerification(UUID requestId) {
186                cancel.execute(validateRequestId(requestId));
187        }
188
189        /**
190         * Move the request onto the next workflow, if available. If successful, this method will return normally.
191         * Otherwise, a {@link VerifyResponseException} will be thrown.
192         *
193         * @param requestId ID of the verify request, obtained from {@link VerificationResponse#getRequestId()}.
194         *
195         * @throws VerifyResponseException If the workflow could not be advanced. This could be for the following reasons:
196         * <ul>
197         *      <li><b>401</b>: Invalid credentials.</li>
198         *      <li><b>404</b>: Request ID was not found or it has been verified already.</li>
199         *      <li><b>409</b>: There are no more events left to trigger.</li>
200         *      <li><b>500</b>: An error occurred on the Vonage platform.</li>
201         * </ul>
202         *
203         * @since 8.5.0
204         */
205        public void nextWorkflow(UUID requestId) {
206                nextWorkflow.execute(validateRequestId(requestId));
207        }
208
209        /**
210         * Create a new custom template.
211         *
212         * @param name Reference name for the template. Must not contain spaces or special characters other than _ and -.
213         *
214         * @return The created template metadata.
215         *
216         * @throws VerifyResponseException If the template could not be created. This could be for the following reasons:
217         * <ul>
218         *     <li><b>401</b>: Invalid credentials.</li>
219         *     <li><b>402</b>: Low balance.</li>
220         *     <li><b>403</b>: Template management is not enabled for your account.</li>
221         *     <li><b>409</b>: A template with the same name already exists, or you have more than 9 templates.</li>
222         *     <li><b>429</b>: Rate limit hit. Please wait and try again.</li>
223         *     <li><b>500</b>: An error occurred on the Vonage platform.</li>
224         * </ul>
225         *
226         * @since 8.13.0
227         */
228        public Template createTemplate(String name) {
229                return createTemplate.execute(new Template(Objects.requireNonNull(name, "Name is required."), null, null));
230        }
231
232        /**
233         * List all custom templates associated with the account.
234         *
235         * @return The list of templates.
236         *
237         * @throws VerifyResponseException If the templates could not be retrieved. This could be for the following reasons:
238         * <ul>
239         *     <li><b>401</b>: Invalid credentials.</li>
240         *     <li><b>402</b>: Low balance.</li>
241         *     <li><b>429</b>: Rate limit hit. Please wait and try again.</li>
242         *     <li><b>500</b>: An error occurred on the Vonage platform.</li>
243         * </ul>
244         *
245         * @since 8.13.0
246         */
247        public List<Template> listTemplates() {
248                return listTemplates(1, 100).getTemplates();
249        }
250
251        // Not useful since there can only be 10 templates at a time.
252        ListTemplatesResponse listTemplates(Integer page, Integer pageSize) {
253                return listTemplates.execute(new ListTemplatesRequest(page, pageSize, null));
254        }
255
256        /**
257         * Retrieve a specific template.
258         *
259         * @param templateId ID of the template to retrieve.
260         *
261         * @return The template metadata.
262         *
263         * @throws VerifyResponseException If the template could not be retrieved. This could be for the following reasons:
264         * <ul>
265         *     <li><b>401</b>: Invalid credentials.</li>
266         *     <li><b>402</b>: Low balance.</li>
267         *     <li><b>404</b>: Template ID was not found.</li>
268         *     <li><b>429</b>: Rate limit hit. Please wait and try again.</li>
269         *     <li><b>500</b>: An error occurred on the Vonage platform.</li>
270         * </ul>
271         *
272         * @since 8.13.0
273         */
274        public Template getTemplate(UUID templateId) {
275                return getTemplate.execute(validateTemplateId(templateId));
276        }
277
278        /**
279         * Update an existing template.
280         *
281         * @param templateId ID of the template to update.
282         * @param name New reference name for the template. Must not contain spaces or special characters.
283         * @param isDefault Whether this template should be the default for the account.
284         *
285         * @return The updated template metadata.
286         *
287         * @throws VerifyResponseException If the template could not be updated. This could be for the following reasons:
288         * <ul>
289         *     <li><b>401</b>: Invalid credentials.</li>
290         *     <li><b>402</b>: Low balance.</li>
291         *     <li><b>403</b>: Template management is not enabled for your account.</li>
292         *     <li><b>404</b>: Template ID was not found.</li>
293         *     <li><b>409</b>: A template with the same name already exists, or you have more than 9 templates.</li>
294         *     <li><b>429</b>: Rate limit hit. Please wait and try again.</li>
295         *     <li><b>500</b>: An error occurred on the Vonage platform.</li>
296         * </ul>
297         *
298         * @since 8.13.0
299         */
300        public Template updateTemplate(UUID templateId, String name, Boolean isDefault) {
301                return updateTemplate.execute(new Template(name, isDefault, validateTemplateId(templateId)));
302        }
303
304        /**
305         * Delete a template.
306         *
307         * @param templateId ID of the template to delete.
308         *
309         * @throws VerifyResponseException If the template could not be deleted. This could be for the following reasons:
310         * <ul>
311         *     <li><b>401</b>: Invalid credentials.</li>
312         *     <li><b>402</b>: Low balance.</li>
313         *     <li><b>403</b>: Template management is not enabled for your account.</li>
314         *     <li><b>404</b>: Template not found.</li>
315         *     <li><b>409</b>: Template contains undeleted fragments or is the default.</li>
316         *     <li><b>429</b>: Rate limit hit. Please wait and try again.</li>
317         *     <li><b>500</b>: An error occurred on the Vonage platform.</li>
318         * </ul>
319         *
320         * @since 8.13.0
321         */
322        public void deleteTemplate(UUID templateId) {
323                deleteTemplate.execute(validateTemplateId(templateId));
324        }
325
326        /**
327         * Create a new template fragment.
328         *
329         * @param templateId ID of the template to which the fragment belongs.
330         * @param fragment The fragment to create.
331         *
332         * @return The created fragment metadata.
333         *
334         * @throws VerifyResponseException If the fragment could not be created. This could be for the following reasons:
335         * <ul>
336         *     <li><b>401</b>: Invalid credentials.</li>
337         *     <li><b>402</b>: Low balance.</li>
338         *     <li><b>403</b>: Template management is not enabled for your account.</li>
339         *     <li><b>404</b>: Template ID was not found.</li>
340         *     <li><b>409</b>: Fragment for this channel and locale already exists.</li>
341         *     <li><b>422</b>: Invalid parameters (e.g. unsupported locale or invalid text variables).</li>
342         *     <li><b>429</b>: Rate limit hit. Please wait and try again.</li>
343         *     <li><b>500</b>: An error occurred on the Vonage platform.</li>
344         * </ul>
345         *
346         * @since 8.13.0
347         */
348        public TemplateFragment createTemplateFragment(UUID templateId, TemplateFragment fragment) {
349                Objects.requireNonNull(fragment, "Template fragment is required.").templateId = validateTemplateId(templateId);
350                return createFragment.execute(fragment);
351        }
352
353        /**
354         * List all fragments associated with a template.
355         *
356         * @param templateId ID of the template to list fragments for.
357         *
358         * @return The list of fragments.
359         *
360         * @throws VerifyResponseException If the fragments could not be retrieved. This could be for the following reasons:
361         * <ul>
362         *     <li><b>401</b>: Invalid credentials.</li>
363         *     <li><b>402</b>: Low balance.</li>
364         *     <li><b>404</b>: Template ID was not found.</li>
365         *     <li><b>429</b>: Rate limit hit. Please wait and try again.</li>
366         *     <li><b>500</b>: An error occurred on the Vonage platform.</li>
367         * </ul>
368         *
369         * @since 8.13.0
370         */
371        public List<TemplateFragment> listTemplateFragments(UUID templateId) {
372                return listTemplateFragments(templateId, 1, 1000).getTemplateFragments();
373        }
374
375        // Not useful in the general case to expose.
376        ListTemplateFragmentsResponse listTemplateFragments(UUID templateId, Integer page, Integer pageSize) {
377                return listFragments.execute(new ListTemplatesRequest(page, pageSize, validateTemplateId(templateId)));
378        }
379
380        /**
381         * Retrieve a specific fragment.
382         *
383         * @param templateId ID of the template to which the fragment belongs.
384         * @param fragmentId ID of the fragment to retrieve.
385         *
386         * @return The fragment metadata.
387         *
388         * @throws VerifyResponseException If the fragment could not be retrieved. This could be for the following reasons:
389         * <ul>
390         *     <li><b>401</b>: Invalid credentials.</li>
391         *     <li><b>402</b>: Low balance.</li>
392         *     <li><b>404</b>: Fragment not found for the provided IDs.</li>
393         *     <li><b>429</b>: Rate limit hit. Please wait and try again.</li>
394         *     <li><b>500</b>: An error occurred on the Vonage platform.</li>
395         * </ul>
396         *
397         * @since 8.13.0
398         */
399        public TemplateFragment getTemplateFragment(UUID templateId, UUID fragmentId) {
400                return getFragment.execute(new TemplateFragmentRequestWrapper(
401                                validateTemplateId(templateId), validateFragmentId(fragmentId)
402                ));
403        }
404
405        /**
406         * Update an existing template fragment.
407         *
408         * @param templateId ID of the template to which the fragment belongs.
409         *
410         * @param fragmentId ID of the fragment to update.
411         *
412         * @param text New text for the fragment.
413         * There are 4 reserved variables available to use: ${code}, ${brand}, ${time-limit} and ${time-limit-unit}.
414         *
415         * @return The updated fragment metadata.
416         *
417         * @throws VerifyResponseException If the fragment could not be updated. This could be for the following reasons:
418         * <ul>
419         *     <li><b>401</b>: Invalid credentials.</li>
420         *     <li><b>402</b>: Low balance.</li>
421         *     <li><b>403</b>: Template management is not enabled for your account.</li>
422         *     <li><b>404</b>: Fragment not found for the provided IDs.</li>
423         *     <li><b>409</b>: Fragment for this channel and locale already exists.</li>
424         *     <li><b>422</b>: Invalid parameters (e.g. unsupported locale or invalid text variables).</li>
425         *     <li><b>429</b>: Rate limit hit. Please wait and try again.</li>
426         *     <li><b>500</b>: An error occurred on the Vonage platform.</li>
427         * </ul>
428         *
429         * @since 8.13.0
430         */
431        public TemplateFragment updateTemplateFragment(UUID templateId, UUID fragmentId, String text) {
432                return updateFragment.execute(new TemplateFragment(
433                                text, validateTemplateId(templateId), validateFragmentId(fragmentId)
434                ));
435        }
436
437        /**
438         * Delete a template fragment.
439         *
440         * @param templateId ID of the template to which the fragment belongs.
441         * @param fragmentId ID of the fragment to delete.
442         *
443         * @throws VerifyResponseException If the fragment could not be deleted. This could be for the following reasons:
444         * <ul>
445         *     <li><b>401</b>: Invalid credentials.</li>
446         *     <li><b>402</b>: Low balance.</li>
447         *     <li><b>403</b>: Template management is not enabled for your account.</li>
448         *     <li><b>404</b>: Fragment not found for the provided IDs.</li>
449         *     <li><b>409</b>: Fragment is in use by a template or is the default.</li>
450         *     <li><b>429</b>: Rate limit hit. Please wait and try again.</li>
451         *     <li><b>500</b>: An error occurred on the Vonage platform.</li>
452         * </ul>
453         *
454         * @since 8.13.0
455         */
456        public void deleteTemplateFragment(UUID templateId, UUID fragmentId) {
457                deleteFragment.execute(new TemplateFragmentRequestWrapper(
458                                validateTemplateId(templateId),
459                                validateFragmentId(fragmentId)
460                ));
461        }
462}