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.account;
017
018import com.vonage.client.*;
019import com.vonage.client.auth.SignatureAuthMethod;
020import com.vonage.client.auth.ApiKeyHeaderAuthMethod;
021import com.vonage.client.common.HttpMethod;
022import java.util.List;
023import java.util.Objects;
024import java.util.function.Function;
025import java.util.function.Supplier;
026
027/**
028 * A client for talking to the Vonage Account API. The standard way to obtain an instance of this class is to use {@link
029 * VonageClient#getAccountClient()}.
030 */
031public class AccountClient {
032    final Supplier<String> apiKeyGetter;
033
034    final RestEndpoint<Void, BalanceResponse> balance;
035    final RestEndpoint<PricingRequest, PricingResponse> pricing;
036    final RestEndpoint<ServiceType, FullPricingResponse> fullPricing;
037    final RestEndpoint<PrefixPricingRequest, PrefixPricingResponse> prefixPricing;
038    final RestEndpoint<TopUpRequest, Void> topUp;
039    final RestEndpoint<SettingsRequest, SettingsResponse> settings;
040    final RestEndpoint<String, ListSecretsResponse> listSecrets;
041    final RestEndpoint<SecretRequest, SecretResponse> getSecret;
042    final RestEndpoint<CreateSecretRequest, SecretResponse> createSecret;
043    final RestEndpoint<SecretRequest, Void> revokeSecret;
044
045    /**
046     * Constructor.
047     *
048     * @param wrapper (required) shared HTTP wrapper object used for making REST calls.
049     */
050    @SuppressWarnings("unchecked")
051    public AccountClient(HttpWrapper wrapper) {
052        apiKeyGetter = () -> wrapper.getAuthCollection().getAuth(ApiKeyHeaderAuthMethod.class).getApiKey();
053
054        class Endpoint<T, R> extends DynamicEndpoint<T, R> {
055            static final String SECRETS_PATH = "s/%s/secrets";
056
057            Endpoint(Function<T, String> pathGetter, R... type) {
058                this(pathGetter, HttpMethod.GET, type);
059            }
060            Endpoint(Function<T, String> pathGetter, HttpMethod method, R... type) {
061                this(pathGetter, method, false, method == HttpMethod.POST, type);
062            }
063            Endpoint(Function<T, String> pathGetter, HttpMethod method,
064                     boolean signatureAuth, boolean formEncoded, R... type
065            ) {
066                super(DynamicEndpoint.<T, R> builder(type)
067                        .wrapper(wrapper).requestMethod(method)
068                        .authMethod(ApiKeyHeaderAuthMethod.class, signatureAuth ? SignatureAuthMethod.class : null)
069                        .responseExceptionType(AccountResponseException.class)
070                        .urlFormEncodedContentType(formEncoded).pathGetter((de, req) -> {
071                                HttpConfig config = de.getHttpWrapper().getHttpConfig();
072                                String base = signatureAuth ? config.getApiBaseUri() : config.getRestBaseUri();
073                                return base + "/account" + pathGetter.apply(req);
074                        })
075                );
076            }
077        }
078
079        class SecretsEndpoint<T, R> extends Endpoint<T, R> {
080            SecretsEndpoint(Function<T, String> apiKeyGetter, HttpMethod method, R... type) {
081                super(req -> String.format(SECRETS_PATH, apiKeyGetter.apply(req)), method, true, false, type);
082            }
083        }
084
085        class SecretRequestEndpoint<R> extends Endpoint<SecretRequest, R> {
086            SecretRequestEndpoint(HttpMethod method, R... type) {
087                super(req -> String.format(SECRETS_PATH, req.apiKey) + "/" + req.secretId,
088                        method, true, false, type
089                );
090            }
091        }
092
093        balance = new Endpoint<>(req -> "/get-balance");
094        pricing = new Endpoint<>(req -> "/get-pricing/outbound/" + req.serviceType);
095        fullPricing = new Endpoint<>(req -> "/get-full-pricing/outbound/" + req);
096        prefixPricing = new Endpoint<>(req -> "/get-prefix-pricing/outbound/" + req.serviceType);
097        topUp = new Endpoint<>(req -> "/top-up", HttpMethod.POST);
098        settings = new Endpoint<>(req -> "/settings", HttpMethod.POST);
099        listSecrets = new SecretsEndpoint<>(Function.identity(), HttpMethod.GET);
100        createSecret = new SecretsEndpoint<>(req -> req.apiKey, HttpMethod.POST);
101        getSecret = new SecretRequestEndpoint<>(HttpMethod.GET);
102        revokeSecret = new SecretRequestEndpoint<>(HttpMethod.DELETE);
103    }
104
105    /**
106     * Obtains the current account remaining balance.
107     *
108     * @return The account balance along with other metadata.
109     *
110     * @throws AccountResponseException If the balance could not be retrieved.
111     */
112    public BalanceResponse getBalance() throws AccountResponseException {
113        return balance.execute(null);
114    }
115
116    /**
117     * Obtain pricing data on all supported countries for a specific service type.
118     *
119     * @param service The service type to retrieve pricing and network information for.
120     *
121     * @return The list of pricing information for all supported countries.
122     *
123     * @throws AccountResponseException If the pricing data could not be retrieved.
124     *
125     * @since v7.9.0
126     */
127    public List<PricingResponse> listPriceAllCountries(ServiceType service) {
128        return fullPricing.execute(Objects.requireNonNull(service, "Service type is required.")).countries;
129    }
130
131    /**
132     * Retrieve the voice pricing for a specified country.
133     *
134     * @param country The two-character country code for which you would like to retrieve pricing.
135     *
136     * @return PricingResponse object which contains the results from the API.
137     *
138     * @throws AccountResponseException If there was an error making the request or retrieving the response.
139     */
140    public PricingResponse getVoicePrice(String country) throws AccountResponseException {
141        return pricing.execute(new PricingRequest(country, ServiceType.VOICE));
142    }
143
144    /**
145     * Retrieve the SMS pricing for a specified country.
146     *
147     * @param country The two-character country code for which you would like to retrieve pricing.
148     *
149     * @return PricingResponse object which contains the results from the API.
150     *
151     * @throws AccountResponseException If there was an error making the request or retrieving the response.
152     */
153    public PricingResponse getSmsPrice(String country) throws AccountResponseException {
154        return pricing.execute(new PricingRequest(country, ServiceType.SMS));
155    }
156
157    /**
158     * Retrieve the pricing for a specified prefix.
159     *
160     * @param type   The type of service to retrieve pricing for.
161     * @param prefix The prefix to retrieve the pricing for.
162     *
163     * @return PrefixPricingResponse object which contains the results from the API.
164     *
165     * @throws AccountResponseException If there was an error making the request or retrieving the response.
166     */
167    public PrefixPricingResponse getPrefixPrice(ServiceType type, String prefix) throws AccountResponseException {
168        return prefixPricing.execute(new PrefixPricingRequest(type, prefix));
169    }
170
171    /**
172     * Top-up your account when you have enabled auto-reload in the dashboard. Amount added is based on your initial
173     * reload-enabled payment.
174     *
175     * @param transaction The ID associated with your original auto-reload transaction
176     *
177     * @throws AccountResponseException If there was an error making the request or retrieving the response.
178     */
179    public void topUp(String transaction) throws AccountResponseException {
180        topUp.execute(new TopUpRequest(transaction));
181    }
182
183    /**
184     * List the ID of each secret associated with this account's main API key.
185     *
186     * @return ListSecretsResponse object which contains the results from the API.
187     *
188     * @throws AccountResponseException If there was an error making the request or retrieving the response.
189     *
190     * @since 7.9.0
191     */
192    public ListSecretsResponse listSecrets() throws AccountResponseException {
193        return listSecrets(apiKeyGetter.get());
194    }
195
196    /**
197     * List the ID of each secret associated to the given API key.
198     *
199     * @param apiKey The API key to look up secrets for.
200     *
201     * @return ListSecretsResponse object which contains the results from the API.
202     *
203     * @throws AccountResponseException If there was an error making the request or retrieving the response.
204     */
205    public ListSecretsResponse listSecrets(String apiKey) throws AccountResponseException {
206        if (apiKey == null || apiKey.trim().isEmpty()) {
207            throw new IllegalArgumentException("API key is required.");
208        }
209        return listSecrets.execute(apiKey);
210    }
211
212    /**
213     * Get information for a specific secret id associated with this account's main API key.
214     *
215     * @param secretId The id of the secret to get information on.
216     *
217     * @return SecretResponse object which contains the results from the API.
218     *
219     * @throws AccountResponseException If there was an error making the request or retrieving the response.
220     *
221     * @since 7.9.0
222     */
223    public SecretResponse getSecret(String secretId) throws AccountResponseException {
224        return getSecret(apiKeyGetter.get(), secretId);
225    }
226
227    /**
228     * Get information for a specific secret id associated to a given API key.
229     *
230     * @param apiKey   The API key that the secret is associated to.
231     * @param secretId The id of the secret to get information on.
232     *
233     * @return SecretResponse object which contains the results from the API.
234     *
235     * @throws AccountResponseException If there was an error making the request or retrieving the response.
236     */
237    public SecretResponse getSecret(String apiKey, String secretId) throws AccountResponseException {
238        return getSecret.execute(new SecretRequest(apiKey, secretId));
239    }
240
241    /**
242     * Create a secret to be used with this account's main API key.
243     *
244     * @param secret The contents of the secret.
245     *
246     * @return SecretResponse object which contains the created secret from the API.
247     *
248     * @throws AccountResponseException If there was an error making the request or retrieving the response.
249     *
250     * @since 7.9.0
251     */
252    public SecretResponse createSecret(String secret) throws AccountResponseException {
253        return createSecret(apiKeyGetter.get(), secret);
254    }
255
256    /**
257     * Create a secret to be used with a specific API key.
258     *
259     * @param apiKey The API key that the secret is to be used with.
260     * @param secret The contents of the secret.
261     *
262     * @return SecretResponse object which contains the created secret from the API.
263     *
264     * @throws AccountResponseException If there was an error making the request or retrieving the response.
265     */
266    public SecretResponse createSecret(String apiKey, String secret) throws AccountResponseException {
267        return createSecret.execute(new CreateSecretRequest(apiKey, secret));
268    }
269
270    /**
271     * Revoke a secret associated with this account's main API key.
272     *
273     * @param secretId The id of the secret to revoke.
274     *
275     * @throws AccountResponseException If there was an error making the request or retrieving the response.
276     *
277     * @since 7.9.0
278     */
279    public void revokeSecret(String secretId) throws AccountResponseException {
280        revokeSecret(apiKeyGetter.get(), secretId);
281    }
282
283    /**
284     * Revoke a secret associated with a specific API key.
285     *
286     * @param apiKey   The API key that the secret is associated to.
287     * @param secretId The id of the secret to revoke.
288     *
289     * @throws AccountResponseException If there was an error making the request or retrieving the response.
290     */
291    public void revokeSecret(String apiKey, String secretId) throws AccountResponseException {
292        revokeSecret.execute(new SecretRequest(apiKey, secretId));
293    }
294
295    /**
296     * Updates the account-level incoming SMS URL, as used by the SMS API.
297     *
298     * @param url The URL where Vonage will send a webhook when an incoming SMS is received when a
299     * number-specific URL is not configured. Set to an empty string to unset the value.
300     *
301     * @return A {@link SettingsResponse} containing the newly-updated account settings.
302     *
303     * @throws AccountResponseException If there was an error making the request or retrieving the response.
304     */
305    public SettingsResponse updateSmsIncomingUrl(String url) throws AccountResponseException {
306        return updateSettings(SettingsRequest.withIncomingSmsUrl(url));
307    }
308
309    /**
310     * Updates the account-level delivery receipt URL (mainly used by the SMS API).
311     *
312     * @param url The URL where Vonage will send a webhook when an incoming SMS is received when a
313     * number-specific URL is not configured. Set to an empty string to unset the value.
314     *
315     * @return A {@link SettingsResponse} containing the newly-updated account settings.
316     *
317     * @throws AccountResponseException If there was an error making the request or retrieving the response.
318     */
319    public SettingsResponse updateDeliveryReceiptUrl(String url) throws AccountResponseException {
320        return updateSettings(SettingsRequest.withDeliveryReceiptUrl(url));
321    }
322
323    /**
324     * Updates the account-level settings.
325     *
326     * @param request The {@link SettingsRequest} containing the fields to update.
327     *
328     * @return A {@link SettingsResponse} containing the newly-updated account settings.
329     *
330     * @throws AccountResponseException If there was an error making the request or retrieving the response.
331     *
332     * @deprecated Use {@link #updateSmsIncomingUrl(String)} or {@link #updateDeliveryReceiptUrl(String)} instead.
333     */
334    @Deprecated
335    public SettingsResponse updateSettings(SettingsRequest request) throws AccountResponseException {
336        return settings.execute(Objects.requireNonNull(request, "Settings request cannot be null."));
337    }
338}