All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ning.billing.recurly.RecurlyClient Maven / Gradle / Ivy

There is a newer version: 0.109.5
Show newest version
/*
 * Copyright 2010-2014 Ning, Inc.
 * Copyright 2014-2015 The Billing Project, LLC
 *
 * The Billing Project licenses this file to you under the Apache License, version 2.0
 * (the "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at:
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package com.ning.billing.recurly;

import com.ning.billing.recurly.model.Account;
import com.ning.billing.recurly.model.AccountBalance;
import com.ning.billing.recurly.model.AccountNotes;
import com.ning.billing.recurly.model.Accounts;
import com.ning.billing.recurly.model.AddOn;
import com.ning.billing.recurly.model.AddOns;
import com.ning.billing.recurly.model.Adjustment;
import com.ning.billing.recurly.model.AdjustmentRefund;
import com.ning.billing.recurly.model.Adjustments;
import com.ning.billing.recurly.model.BillingInfo;
import com.ning.billing.recurly.model.BillingInfos;
import com.ning.billing.recurly.model.BillingInfoVerification;
import com.ning.billing.recurly.model.Coupon;
import com.ning.billing.recurly.model.Coupons;
import com.ning.billing.recurly.model.CreditPayments;
import com.ning.billing.recurly.model.DunningCampaign;
import com.ning.billing.recurly.model.DunningCampaignBulkUpdate;
import com.ning.billing.recurly.model.DunningCampaigns;
import com.ning.billing.recurly.model.Errors;
import com.ning.billing.recurly.model.GiftCard;
import com.ning.billing.recurly.model.GiftCards;
import com.ning.billing.recurly.model.Invoice;
import com.ning.billing.recurly.model.InvoiceCollection;
import com.ning.billing.recurly.model.InvoiceRefund;
import com.ning.billing.recurly.model.InvoiceState;
import com.ning.billing.recurly.model.InvoiceTemplate;
import com.ning.billing.recurly.model.InvoiceTemplates;
import com.ning.billing.recurly.model.Invoices;
import com.ning.billing.recurly.model.Item;
import com.ning.billing.recurly.model.Items;
import com.ning.billing.recurly.model.Plan;
import com.ning.billing.recurly.model.Plans;
import com.ning.billing.recurly.model.Purchase;
import com.ning.billing.recurly.model.RecurlyAPIError;
import com.ning.billing.recurly.model.RecurlyObject;
import com.ning.billing.recurly.model.RecurlyObjects;
import com.ning.billing.recurly.model.Redemption;
import com.ning.billing.recurly.model.Redemptions;
import com.ning.billing.recurly.model.RefundMethod;
import com.ning.billing.recurly.model.RefundOption;
import com.ning.billing.recurly.model.ShippingAddress;
import com.ning.billing.recurly.model.ShippingAddresses;
import com.ning.billing.recurly.model.Subscription;
import com.ning.billing.recurly.model.SubscriptionState;
import com.ning.billing.recurly.model.SubscriptionUpdate;
import com.ning.billing.recurly.model.SubscriptionNotes;
import com.ning.billing.recurly.model.Subscriptions;
import com.ning.billing.recurly.model.Transaction;
import com.ning.billing.recurly.model.TransactionState;
import com.ning.billing.recurly.model.TransactionType;
import com.ning.billing.recurly.model.Transactions;
import com.ning.billing.recurly.model.Usage;
import com.ning.billing.recurly.model.Usages;
import com.ning.billing.recurly.model.MeasuredUnit;
import com.ning.billing.recurly.model.MeasuredUnits;
import com.ning.billing.recurly.model.AccountAcquisition;
import com.ning.billing.recurly.model.ShippingMethod;
import com.ning.billing.recurly.model.ShippingMethods;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.base.MoreObjects;
import com.google.common.base.StandardSystemProperty;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.BaseEncoding;
import com.google.common.net.HttpHeaders;
import com.ning.billing.recurly.util.http.SslUtils;

import org.apache.commons.codec.net.URLCodec;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NoHttpResponseException;
import org.apache.http.ParseException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.HeaderGroup;
import org.apache.http.util.EntityUtils;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import javax.net.ssl.SSLException;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.ConnectException;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.BitSet;
import java.util.List;
import java.util.Locale;

public class RecurlyClient {

    private static final Logger log = LoggerFactory.getLogger(RecurlyClient.class);

    public static final String RECURLY_DEBUG_KEY = "recurly.debug";
    public static final String RECURLY_API_VERSION = "2.29";

    private static final String X_RATELIMIT_REMAINING_HEADER_NAME = "X-RateLimit-Remaining";
    private static final String X_RECORDS_HEADER_NAME = "X-Records";
    private static final String LINK_HEADER_NAME = "Link";

    private static final String GIT_PROPERTIES_FILE = "com/ning/billing/recurly/git.properties";
    @VisibleForTesting
    static final String GIT_COMMIT_ID_DESCRIBE_SHORT = "git.commit.id.describe-short";
    private static final Pattern TAG_FROM_GIT_DESCRIBE_PATTERN = Pattern.compile("recurly-java-library-([0-9]*\\.[0-9]*\\.[0-9]*)(-[0-9]*)?");

    public static final String FETCH_RESOURCE = "/recurly_js/result";

    private static final Set validHosts = ImmutableSet.of("recurly.com");

    /**
     * RFC-3986 unreserved characters used for standard URL encoding.
* Source */ private static final BitSet RFC_3986_SAFE_CHARS; static { RFC_3986_SAFE_CHARS = new BitSet(256); RFC_3986_SAFE_CHARS.set('a', 'z' + 1); RFC_3986_SAFE_CHARS.set('A', 'Z' + 1); RFC_3986_SAFE_CHARS.set('0', '9' + 1); RFC_3986_SAFE_CHARS.set('-'); RFC_3986_SAFE_CHARS.set('_'); RFC_3986_SAFE_CHARS.set('.'); RFC_3986_SAFE_CHARS.set('~'); } /** * Checks a system property to see if debugging output is * required. Used internally by the client to decide whether to * generate debug output */ private static boolean debug() { return Boolean.getBoolean(RECURLY_DEBUG_KEY); } /** * Warns the user about logging PII in production environments */ private static void loggerWarning() { if (debug()) { log.warn("[WARNING] Logger enabled. The logger has the potential to leak " + "PII and should never be used in production environments."); } } private final String userAgent; private final String key; private final String baseUrl; private CloseableHttpClient client; // Allows error messages to be returned in a specified language private String acceptLanguage = "en-US"; // Stores the number of requests remaining before rate limiting takes effect private int rateLimitRemaining; public RecurlyClient(final String apiKey) { this(apiKey, "api"); loggerWarning(); } public RecurlyClient(final String apiKey, final String subDomain) { this(apiKey, subDomain + ".recurly.com", 443, "v2"); loggerWarning(); } public RecurlyClient(final String apiKey, final String host, final int port, final String version) { this(apiKey, "https", host, port, version); loggerWarning(); } public RecurlyClient(final String apiKey, final String scheme, final String host, final int port, final String version) { this.key = BaseEncoding.base64().encode(apiKey.getBytes(Charsets.UTF_8)); this.baseUrl = String.format(Locale.ROOT, "%s://%s:%d/%s", scheme, host, port, version); this.userAgent = UserAgentHolder.userAgent; this.rateLimitRemaining = -1; loggerWarning(); } /** * Open the underlying http client */ public synchronized void open() throws NoSuchAlgorithmException, KeyManagementException { client = createHttpClient(); } /** * Close the underlying http client */ public synchronized void close() { if (client != null) { try { client.close(); } catch (IOException e) { throw new RuntimeException(e); } } } /** * Set the Accept-Language header *

* Sets the Accept-Language header for all requests made by this client. Note: this is not thread-safe! * See https://github.com/killbilling/recurly-java-library/pull/298 for more details about thread safety. * * @param language The language to set in the header. E.g., "en-US" */ public void setAcceptLanguage(String language) { this.acceptLanguage = language; } /** * Returns the number of requests remaining until requests will be denied by rate limiting. * @return Number of requests remaining. Value is valid (> -1) after a successful API call. */ public int getRateLimitRemaining() { return rateLimitRemaining; } /** * Create Account *

* Creates a new account. You may optionally include billing information. * * @param account account object * @return the newly created account object on success, null otherwise */ public Account createAccount(final Account account) { return doPOST(Account.ACCOUNT_RESOURCE, account, Account.class); } /** * Get Accounts *

* Returns information about all accounts. * * @return Accounts on success, null otherwise */ public Accounts getAccounts() { return doGET(Accounts.ACCOUNTS_RESOURCE, Accounts.class, new QueryParams()); } /** * Get Accounts given query params *

* Returns information about all accounts. * * @param params {@link QueryParams} * @return Accounts on success, null otherwise */ public Accounts getAccounts(final QueryParams params) { return doGET(Accounts.ACCOUNTS_RESOURCE, Accounts.class, params); } /** * Get number of Accounts matching the query params * * @param params {@link QueryParams} * @return Integer on success, null otherwise */ public Integer getAccountsCount(final QueryParams params) { HeaderGroup map = doHEAD(Accounts.ACCOUNTS_RESOURCE, params); return Integer.parseInt(map.getFirstHeader(X_RECORDS_HEADER_NAME).getValue()); } /** * Get Coupons *

* Returns information about all accounts. * * @return Coupons on success, null otherwise */ public Coupons getCoupons() { return doGET(Coupons.COUPONS_RESOURCE, Coupons.class, new QueryParams()); } /** * Get Coupons given query params *

* Returns information about all accounts. * * @param params {@link QueryParams} * @return Coupons on success, null otherwise */ public Coupons getCoupons(final QueryParams params) { return doGET(Coupons.COUPONS_RESOURCE, Coupons.class, params); } /** * Get number of Coupons matching the query params * * @param params {@link QueryParams} * @return Integer on success, null otherwise */ public Integer getCouponsCount(final QueryParams params) { HeaderGroup map = doHEAD(Coupons.COUPONS_RESOURCE, params); return Integer.parseInt(map.getFirstHeader(X_RECORDS_HEADER_NAME).getValue()); } /** * Get Account *

* Returns information about a single account. * * @param accountCode recurly account id * @return account object on success, null otherwise */ public Account getAccount(final String accountCode) { if (accountCode == null || accountCode.isEmpty()) throw new RuntimeException("accountCode cannot be empty!"); return doGET(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode), Account.class); } /** * Update Account *

* Updates an existing account. * * @param accountCode recurly account id * @param account account object * @return the updated account object on success, null otherwise */ public Account updateAccount(final String accountCode, final Account account) { return doPUT(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode), account, Account.class); } /** * Get Account Balance *

* Retrieves the remaining balance on the account * * @param accountCode recurly account id * @return the updated AccountBalance if success, null otherwise */ public AccountBalance getAccountBalance(final String accountCode) { return doGET(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + AccountBalance.ACCOUNT_BALANCE_RESOURCE, AccountBalance.class); } /** * Close Account *

* Marks an account as closed and cancels any active subscriptions. Any saved billing information will also be * permanently removed from the account. * * @param accountCode recurly account id */ public void closeAccount(final String accountCode) { doDELETE(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode)); } /** * Reopen Account *

* Transitions a closed account back to active. * * @param accountCode recurly account id */ public Account reopenAccount(final String accountCode) { return doPUT(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + "/reopen", null, Account.class); } /** * Get Child Accounts *

* Returns information about a the child accounts of an account. * * @param accountCode recurly account id * @return Accounts on success, null otherwise */ public Accounts getChildAccounts(final String accountCode) { return doGET(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + "/child_accounts", Accounts.class, new QueryParams()); } //////////////////////////////////////////////////////////////////////////////////////// // Account adjustments /** * Get Account Adjustments *

* * @param accountCode recurly account id * @return the adjustments on the account */ public Adjustments getAccountAdjustments(final String accountCode) { return getAccountAdjustments(accountCode, null, null, new QueryParams()); } /** * Get Account Adjustments *

* * @param accountCode recurly account id * @param type {@link com.ning.billing.recurly.model.Adjustments.AdjustmentType} * @return the adjustments on the account */ public Adjustments getAccountAdjustments(final String accountCode, final Adjustments.AdjustmentType type) { return getAccountAdjustments(accountCode, type, null, new QueryParams()); } /** * Get Account Adjustments *

* * @param accountCode recurly account id * @param type {@link com.ning.billing.recurly.model.Adjustments.AdjustmentType} * @param state {@link com.ning.billing.recurly.model.Adjustments.AdjustmentState} * @return the adjustments on the account */ public Adjustments getAccountAdjustments(final String accountCode, final Adjustments.AdjustmentType type, final Adjustments.AdjustmentState state) { return getAccountAdjustments(accountCode, type, state, new QueryParams()); } /** * Get Account Adjustments *

* * @param accountCode recurly account id * @param type {@link com.ning.billing.recurly.model.Adjustments.AdjustmentType} * @param state {@link com.ning.billing.recurly.model.Adjustments.AdjustmentState} * @param params {@link QueryParams} * @return the adjustments on the account */ public Adjustments getAccountAdjustments(final String accountCode, final Adjustments.AdjustmentType type, final Adjustments.AdjustmentState state, final QueryParams params) { final String url = Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + Adjustments.ADJUSTMENTS_RESOURCE; if (type != null) params.put("type", type.getType()); if (state != null) params.put("state", state.getState()); return doGET(url, Adjustments.class, params); } public Adjustment getAdjustment(final String adjustmentUuid) { if (adjustmentUuid == null || adjustmentUuid.isEmpty()) throw new RuntimeException("adjustmentUuid cannot be empty!"); return doGET(Adjustments.ADJUSTMENTS_RESOURCE + "/" + urlEncode(adjustmentUuid), Adjustment.class); } public Adjustment createAccountAdjustment(final String accountCode, final Adjustment adjustment) { return doPOST(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + Adjustments.ADJUSTMENTS_RESOURCE, adjustment, Adjustment.class); } public void deleteAccountAdjustment(final String accountCode) { doDELETE(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + Adjustments.ADJUSTMENTS_RESOURCE); } public void deleteAdjustment(final String adjustmentUuid) { doDELETE(Adjustments.ADJUSTMENTS_RESOURCE + "/" + urlEncode(adjustmentUuid)); } //////////////////////////////////////////////////////////////////////////////////////// /** * Create a subscription *

* Creates a subscription for an account. * * @param subscription Subscription object * @return the newly created Subscription object on success, null otherwise */ public Subscription createSubscription(final Subscription subscription) { return doPOST(Subscription.SUBSCRIPTION_RESOURCE, subscription, Subscription.class); } /** * Preview a subscription *

* Previews a subscription for an account. * * @param subscription Subscription object * @return the newly created Subscription object on success, null otherwise */ public Subscription previewSubscription(final Subscription subscription) { return doPOST(Subscription.SUBSCRIPTION_RESOURCE + "/preview", subscription, Subscription.class); } /** * Get a particular {@link Subscription} by it's UUID *

* Returns information about a single subscription. * * @param uuid UUID of the subscription to lookup * @return Subscription */ public Subscription getSubscription(final String uuid) { if (uuid == null || uuid.isEmpty()) throw new RuntimeException("uuid cannot be empty!"); return doGET(Subscriptions.SUBSCRIPTIONS_RESOURCE + "/" + urlEncode(uuid), Subscription.class); } /** * Cancel a subscription *

* Cancel a subscription so it remains active and then expires at the end of the current bill cycle. * * @param subscription Subscription object * @return Subscription */ public Subscription cancelSubscription(final Subscription subscription) { return doPUT(Subscription.SUBSCRIPTION_RESOURCE + "/" + urlEncode(subscription.getUuid()) + "/cancel", subscription, Subscription.class); } /** * Cancel a subscription *

* Cancel a subscription so it remains active and then expires at the end of the current bill cycle. * * @param subscriptionUuid String uuid of the subscription to cancel * @param timeframe SubscriptionUpdate.TimeFrame the timeframe in which to cancel. Only accepts bill_date or term_end * @return Subscription */ public Subscription cancelSubscription(final String subscriptionUuid, final SubscriptionUpdate.Timeframe timeframe) { final QueryParams qp = new QueryParams(); if (timeframe != null) qp.put("timeframe", timeframe.toString()); return doPUT(Subscription.SUBSCRIPTION_RESOURCE + "/" + urlEncode(subscriptionUuid) + "/cancel", null, Subscription.class, qp); } /** * Pause a subscription or cancel a scheduled pause on a subscription. *

* * For an active subscription without a pause scheduled already, this will * schedule a pause period to begin at the next renewal date for the specified * number of billing cycles (remaining_pause_cycles). * * When a scheduled pause already exists, this will update the remaining pause * cycles with the new value sent. When zero (0) remaining_pause_cycles is sent * for a subscription with a scheduled pause, the pause will be canceled. * * For a paused subscription, the remaining_pause_cycles will adjust the * length of the current pause period. Sending zero (0) in the remaining_pause_cycles * field will cause the subscription to be resumed at the next renewal date. * * @param subscriptionUuid The uuid for the subscription you wish to pause. * @param remainingPauseCycles The number of billing cycles that the subscription will be paused. * @return Subscription */ public Subscription pauseSubscription(final String subscriptionUuid, final int remainingPauseCycles) { Subscription request = new Subscription(); request.setRemainingPauseCycles(remainingPauseCycles); return doPUT(Subscription.SUBSCRIPTION_RESOURCE + "/" + urlEncode(subscriptionUuid) + "/pause", request, Subscription.class); } /** * Convert trial to paid subscription when TransactionType = "moto". * @param subscriptionUuid The uuid for the subscription you want to convert from trial to paid. * @return Subscription */ public Subscription convertTrialMoto(final String subscriptionUuid) { Subscription request = new Subscription(); request.setTransactionType("moto"); return doPUT(Subscription.SUBSCRIPTION_RESOURCE + "/" + urlEncode(subscriptionUuid) + "/convert_trial", request, Subscription.class); } /** * Convert trial to paid subscription without 3DS token * @param subscriptionUuid The uuid for the subscription you want to convert from trial to paid. * @return Subscription */ public Subscription convertTrial(final String subscriptionUuid) { return convertTrial(subscriptionUuid, null); } /** * Convert trial to paid subscription with 3DS token * @param subscriptionUuid The uuid for the subscription you want to convert from trial to paid. * @param ThreeDSecureActionResultTokenId 3DS secure action result token id in billing info. * @return Subscription */ public Subscription convertTrial(final String subscriptionUuid, final String ThreeDSecureActionResultTokenId) { Subscription request; if (ThreeDSecureActionResultTokenId == null) { request = null; } else { request = new Subscription(); Account account = new Account(); BillingInfo billingInfo = new BillingInfo(); billingInfo.setThreeDSecureActionResultTokenId(ThreeDSecureActionResultTokenId); account.setBillingInfo(billingInfo); request.setAccount(account); } return doPUT(Subscription.SUBSCRIPTION_RESOURCE + "/" + urlEncode(subscriptionUuid) + "/convert_trial", request, Subscription.class); } /** * Immediately resumes a currently paused subscription. *

* For a paused subscription, this will immediately resume the subscription * from the pause, produce an invoice, and return the newly resumed subscription. * Any at-renewal subscription changes will be immediately applied when * the subscription resumes. * * @param subscriptionUuid The uuid for the subscription you wish to pause. * @return Subscription */ public Subscription resumeSubscription(final String subscriptionUuid) { return doPUT(Subscription.SUBSCRIPTION_RESOURCE + "/" + urlEncode(subscriptionUuid) + "/resume", null, Subscription.class); } /** * Postpone a subscription *

* postpone a subscription, setting a new renewal date. * * @param subscription Subscription object * @return Subscription */ public Subscription postponeSubscription(final Subscription subscription, final DateTime nextBillDate) { final QueryParams params = new QueryParams(); params.put("next_bill_date", nextBillDate.toString()); return doPUT(Subscription.SUBSCRIPTION_RESOURCE + "/" + urlEncode(subscription.getUuid()) + "/postpone", subscription, Subscription.class, params); } /** * Terminate a particular {@link Subscription} by it's UUID * * @param subscription Subscription to terminate */ public void terminateSubscription(final Subscription subscription, final RefundOption refund) { final QueryParams params = new QueryParams(); params.put("refund", refund.toString()); doPUT(Subscription.SUBSCRIPTION_RESOURCE + "/" + urlEncode(subscription.getUuid()) + "/terminate", subscription, Subscription.class, params); } /** * Reactivating a canceled subscription *

* Reactivate a canceled subscription so it renews at the end of the current bill cycle. * * @param subscription Subscription object * @return Subscription */ public Subscription reactivateSubscription(final Subscription subscription) { return doPUT(Subscription.SUBSCRIPTION_RESOURCE + "/" + urlEncode(subscription.getUuid()) + "/reactivate", subscription, Subscription.class); } /** * Update a particular {@link Subscription} by it's UUID *

* Returns information about a single subscription. * * @param uuid UUID of the subscription to update * @param subscriptionUpdate subscriptionUpdate object * @return Subscription the updated subscription */ public Subscription updateSubscription(final String uuid, final SubscriptionUpdate subscriptionUpdate) { return doPUT(Subscriptions.SUBSCRIPTIONS_RESOURCE + "/" + urlEncode(uuid), subscriptionUpdate, Subscription.class); } /** * Preview an update to a particular {@link Subscription} by it's UUID *

* Returns information about a single subscription. * * @param uuid UUID of the subscription to preview an update for * @return Subscription the updated subscription preview */ public Subscription updateSubscriptionPreview(final String uuid, final SubscriptionUpdate subscriptionUpdate) { return doPOST(Subscriptions.SUBSCRIPTIONS_RESOURCE + "/" + urlEncode(uuid) + "/preview", subscriptionUpdate, Subscription.class); } /** * Update to a particular {@link Subscription}'s notes by it's UUID *

* Returns information about a single subscription. * * @param uuid UUID of the subscription to preview an update for * @param subscriptionNotes SubscriptionNotes object * @return Subscription the updated subscription */ public Subscription updateSubscriptionNotes(final String uuid, final SubscriptionNotes subscriptionNotes) { return doPUT(SubscriptionNotes.SUBSCRIPTION_RESOURCE + "/" + urlEncode(uuid) + "/notes", subscriptionNotes, Subscription.class); } /** * Get the subscriptions for an {@link Account}. *

* Returns subscriptions associated with an account * * @param accountCode recurly account id * @return Subscriptions on the account */ public Subscriptions getAccountSubscriptions(final String accountCode) { return doGET(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + Subscriptions.SUBSCRIPTIONS_RESOURCE, Subscriptions.class, new QueryParams()); } /** * Get all the subscriptions on the site *

* Returns all the subscriptions on the site * * @return Subscriptions on the site */ public Subscriptions getSubscriptions() { return doGET(Subscriptions.SUBSCRIPTIONS_RESOURCE, Subscriptions.class, new QueryParams()); } /** * Get all the subscriptions on the site given some sort and filter params. *

* Returns all the subscriptions on the site * * @param state {@link SubscriptionState} * @param params {@link QueryParams} * @return Subscriptions on the site */ public Subscriptions getSubscriptions(final SubscriptionState state, final QueryParams params) { if (state != null) { params.put("state", state.getType()); } return doGET(Subscriptions.SUBSCRIPTIONS_RESOURCE, Subscriptions.class, params); } /** * Get number of Subscriptions matching the query params * * @param params {@link QueryParams} * @return Integer on success, null otherwise */ public Integer getSubscriptionsCount(final QueryParams params) { HeaderGroup map = doHEAD(Subscription.SUBSCRIPTION_RESOURCE, params); return Integer.parseInt(map.getFirstHeader(X_RECORDS_HEADER_NAME).getValue()); } /** * Get the subscriptions for an {@link Account} given query params *

* Returns subscriptions associated with an account * * @param accountCode recurly account id * @param state {@link SubscriptionState} * @param params {@link QueryParams} * @return Subscriptions on the account */ public Subscriptions getAccountSubscriptions(final String accountCode, final SubscriptionState state, final QueryParams params) { if (state != null) params.put("state", state.getType()); return doGET(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + Subscriptions.SUBSCRIPTIONS_RESOURCE, Subscriptions.class, params); } /** * Return all the subscriptions on an invoice. * * @param invoiceId String Recurly Invoice ID * @return all the subscriptions on the invoice */ public Subscriptions getInvoiceSubscriptions(final String invoiceId) { return getInvoiceSubscriptions(invoiceId, new QueryParams()); } /** * Return all the subscriptions on an invoice given query params. * * @param invoiceId String Recurly Invoice ID * @param params {@link QueryParams} * @return all the subscriptions on the invoice */ public Subscriptions getInvoiceSubscriptions(final String invoiceId, final QueryParams params) { return doGET(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId) + Subscriptions.SUBSCRIPTIONS_RESOURCE, Subscriptions.class, params); } /** * Post usage to subscription *

* * @param subscriptionCode The recurly id of the {@link Subscription } * @param addOnCode recurly id of {@link AddOn} * @param usage the usage to post on recurly * @return the {@link Usage} object as identified by the passed in object */ public Usage postSubscriptionUsage(final String subscriptionCode, final String addOnCode, final Usage usage) { return doPOST(Subscription.SUBSCRIPTION_RESOURCE + "/" + urlEncode(subscriptionCode) + AddOn.ADDONS_RESOURCE + "/" + urlEncode(addOnCode) + Usage.USAGE_RESOURCE, usage, Usage.class); } /** * Get Subscription Addon Usages *

* * @param subscriptionCode The recurly id of the {@link Subscription } * @param addOnCode recurly id of {@link AddOn} * @return {@link Usages} for the specified subscription and addOn */ public Usages getSubscriptionUsages(final String subscriptionCode, final String addOnCode, final QueryParams params) { return doGET(Subscription.SUBSCRIPTION_RESOURCE + "/" + urlEncode(subscriptionCode) + AddOn.ADDONS_RESOURCE + "/" + urlEncode(addOnCode) + Usage.USAGE_RESOURCE, Usages.class, params ); } /** * Get the subscriptions for an account. * This is deprecated. Please use getAccountSubscriptions(String, Subscriptions.State, QueryParams) *

* Returns information about a single account. * * @param accountCode recurly account id * @param status Only accounts in this status will be returned * @return Subscriptions on the account */ @Deprecated public Subscriptions getAccountSubscriptions(final String accountCode, final String status) { final QueryParams params = new QueryParams(); if (status != null) params.put("state", status); return doGET(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + Subscriptions.SUBSCRIPTIONS_RESOURCE, Subscriptions.class, params); } //////////////////////////////////////////////////////////////////////////////////////// /** * Get DunningCampaigns *

* Returns all active campaigns on a site. * * @return DunningCampaigns on success, null otherwise */ public DunningCampaigns getDunningCampaigns() { return doGET(DunningCampaigns.DUNNING_CAMPAIGNS_RESOURCE, DunningCampaigns.class, new QueryParams()); } /** * Get DunningCampaign *

* Returns information about a single dunning campaign. * * @param id dunning campaign uuid * @return dunning campaign object on success, null otherwise */ public DunningCampaign getDunningCampaign(final String id) { if (id == null || id.isEmpty()) throw new RuntimeException("id cannot be empty!"); return doGET(DunningCampaign.DUNNING_CAMPAIGNS_RESOURCE + "/" + urlEncode(id), DunningCampaign.class); } /** * Update to a particular {@link Plan}'s dunning_campaign by its id *

* Returns no content. * * @param id dunning campaign uuid * @param dunningCampaignBulkUpdate DunningCampaignBulkUpdate object containing planCodes to update */ public void bulkUpdate(final String id, final DunningCampaignBulkUpdate dunningCampaignBulkUpdate) { doPUT(DunningCampaign.DUNNING_CAMPAIGNS_RESOURCE + "/" + urlEncode(id) + "/bulk_update", dunningCampaignBulkUpdate, DunningCampaignBulkUpdate.class); } //////////////////////////////////////////////////////////////////////////////////////// /** * Get InvoiceTemplates *

* Returns all invoice templates on a site. * * @return InvoiceTemplates on success, null otherwise */ public InvoiceTemplates getInvoiceTemplates() { return doGET(InvoiceTemplates.INVOICE_TEMPLATES_RESOURCE, InvoiceTemplates.class, new QueryParams()); } /** * Get InvoiceTemplate *

* Returns information about a single invoice template. * * @param uuid dunning campaign uuid * @return dunning campaign object on success, null otherwise */ public InvoiceTemplate getInvoiceTemplate(final String uuid) { if (uuid == null || uuid.isEmpty()) throw new RuntimeException("uuid cannot be empty!"); return doGET(InvoiceTemplate.INVOICE_TEMPLATES_RESOURCE + "/" + urlEncode(uuid), InvoiceTemplate.class); } /** * Get the acccounts for a {@link InvoiceTemplate}. *

* Returns accounts associated with a invoice template * * @param accountCode recurly invoice template uuid * @param params {@link QueryParams} * @return Accounts on the invoice template */ public Accounts getInvoiceTemplateAccounts(final String invoiceTemplateUuid, final QueryParams params) { return doGET(InvoiceTemplate.INVOICE_TEMPLATES_RESOURCE + "/" + urlEncode(invoiceTemplateUuid) + Accounts.ACCOUNTS_RESOURCE, Accounts.class, params); } //////////////////////////////////////////////////////////////////////////////////////// /** * Update an account's billing info *

* When new or updated credit card information is updated, the billing information is only saved if the credit card * is valid. If the account has a past due invoice, the outstanding balance will be collected to validate the * billing information. *

* If the account does not exist before the API request, the account will be created if the billing information * is valid. *

* Please note: this API end-point may be used to import billing information without security codes (CVV). * Recurly recommends requiring CVV from your customers when collecting new or updated billing information. * * @param accountCode recurly account id * @param billingInfo billing info object to create or update * @return the newly created or update billing info object on success, null otherwise */ public BillingInfo createOrUpdateBillingInfo(final String accountCode, final BillingInfo billingInfo) { return doPUT(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + BillingInfo.BILLING_INFO_RESOURCE, billingInfo, BillingInfo.class); } public BillingInfo createBillingInfo(final String accountCode, final BillingInfo billingInfo) { return doPOST(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + BillingInfos.BILLING_INFOS_RESOURCE, billingInfo, BillingInfo.class); } public BillingInfo updateBillingInfo(final String accountCode, final String uuid, final BillingInfo billingInfo) { return doPUT(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + BillingInfos.BILLING_INFOS_RESOURCE + "/" + urlEncode(uuid), billingInfo, BillingInfo.class); } public BillingInfo getBillingInfoByUuid(final String accountCode, final String uuid) { return doGET(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + BillingInfos.BILLING_INFOS_RESOURCE + "/" + urlEncode(uuid), BillingInfo.class); } public BillingInfos getBillingInfos(final String accountCode) { return doGET(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + BillingInfos.BILLING_INFOS_RESOURCE, BillingInfos.class); } public void deleteBillingInfo(final String accountCode, final String uuid) { doDELETE(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + BillingInfos.BILLING_INFOS_RESOURCE + "/" + uuid); } /** * Update an account's billing info *

* When new or updated credit card information is updated, the billing information is only saved if the credit card * is valid. If the account has a past due invoice, the outstanding balance will be collected to validate the * billing information. *

* If the account does not exist before the API request, the account will be created if the billing information * is valid. *

* Please note: this API end-point may be used to import billing information without security codes (CVV). * Recurly recommends requiring CVV from your customers when collecting new or updated billing information. * * @deprecated Replaced by {@link #createOrUpdateBillingInfo(String, BillingInfo)} Please pass in the account code rather than setting the account on the BillingInfo object * * @param billingInfo billing info object to create or update * @return the newly created or update billing info object on success, null otherwise */ @Deprecated public BillingInfo createOrUpdateBillingInfo(final BillingInfo billingInfo) { final String accountCode = billingInfo.getAccount().getAccountCode(); // Unset it to avoid confusing Recurly billingInfo.setAccount(null); return doPUT(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + BillingInfo.BILLING_INFO_RESOURCE, billingInfo, BillingInfo.class); } /** * Lookup an account's billing info *

* Returns only the account's current billing information. * * @param accountCode recurly account id * @return the current billing info object associated with this account on success, null otherwise */ public BillingInfo getBillingInfo(final String accountCode) { return doGET(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + BillingInfo.BILLING_INFO_RESOURCE, BillingInfo.class); } /** * Clear an account's billing info *

* You may remove any stored billing information for an account. If the account has a subscription, the renewal will * go into past due unless you update the billing info before the renewal occurs * * @param accountCode recurly account id */ public void clearBillingInfo(final String accountCode) { doDELETE(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + BillingInfo.BILLING_INFO_RESOURCE); } /** * Verify an account's billing info *

* Verifies an account's billing info without providing a specific gateway. * @param accountCode recurly account id * @return the transaction generated from the verification */ public Transaction verifyBillingInfo(final String accountCode) { final String url = Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + BillingInfo.BILLING_INFO_RESOURCE + "/verify"; final BillingInfoVerification gateway = new BillingInfoVerification(); return doPOST(url, gateway, Transaction.class); } /** * Verify an account's billing info *

* Verifies an account's billing info using a gateway code param. * @param accountCode recurly account id * @param gatewayVerification BillingInfoVerification object used to verify billing info * @return the transaction generated from the verification */ public Transaction verifyBillingInfo(final String accountCode, final BillingInfoVerification gatewayVerification) { final String url = Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + BillingInfo.BILLING_INFO_RESOURCE + "/verify"; return doPOST(url, gatewayVerification, Transaction.class); } /////////////////////////////////////////////////////////////////////////// // Account Notes /** * List an account's notes *

* Returns the account's notes * * @param accountCode recurly account id * @return the notes associated with this account on success, null otherwise */ public AccountNotes getAccountNotes(final String accountCode) { return doGET(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + AccountNotes.ACCOUNT_NOTES_RESOURCE, AccountNotes.class, new QueryParams()); } /////////////////////////////////////////////////////////////////////////// // User transactions /** * Lookup an account's transactions history *

* Returns the account's transaction history * * @param accountCode recurly account id * @return the transaction history associated with this account on success, null otherwise */ public Transactions getAccountTransactions(final String accountCode) { return doGET(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + Transactions.TRANSACTIONS_RESOURCE, Transactions.class, new QueryParams()); } /** * Lookup an account's transactions history given query params *

* Returns the account's transaction history * * @param accountCode recurly account id * @param state {@link TransactionState} * @param type {@link TransactionType} * @param params {@link QueryParams} * @return the transaction history associated with this account on success, null otherwise */ public Transactions getAccountTransactions(final String accountCode, final TransactionState state, final TransactionType type, final QueryParams params) { if (state != null) params.put("state", state.getType()); if (type != null) params.put("type", type.getType()); return doGET(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + Transactions.TRANSACTIONS_RESOURCE, Transactions.class, params); } /** * Get site's transaction history *

* All transactions on the site * * @return the transaction history of the site on success, null otherwise */ public Transactions getTransactions() { return doGET(Transactions.TRANSACTIONS_RESOURCE, Transactions.class, new QueryParams()); } /** * Get site's transaction history *

* All transactions on the site * * @param state {@link TransactionState} * @param type {@link TransactionType} * @param params {@link QueryParams} * @return the transaction history of the site on success, null otherwise */ public Transactions getTransactions(final TransactionState state, final TransactionType type, final QueryParams params) { if (state != null) params.put("state", state.getType()); if (type != null) params.put("type", type.getType()); return doGET(Transactions.TRANSACTIONS_RESOURCE, Transactions.class, params); } /** * Get number of Transactions matching the query params * * @param params {@link QueryParams} * @return Integer on success, null otherwise */ public Integer getTransactionsCount(final QueryParams params) { HeaderGroup map = doHEAD(Transactions.TRANSACTIONS_RESOURCE, params); return Integer.parseInt(map.getFirstHeader(X_RECORDS_HEADER_NAME).getValue()); } /** * Lookup a transaction * * @param transactionId recurly transaction id * @return the transaction if found, null otherwise */ public Transaction getTransaction(final String transactionId) { if (transactionId == null || transactionId.isEmpty()) throw new RuntimeException("transactionId cannot be empty!"); return doGET(Transactions.TRANSACTIONS_RESOURCE + "/" + urlEncode(transactionId), Transaction.class); } /** * Creates a {@link Transaction} through the Recurly API. * * @param trans The {@link Transaction} to create * @return The created {@link Transaction} object */ public Transaction createTransaction(final Transaction trans) { return doPOST(Transactions.TRANSACTIONS_RESOURCE, trans, Transaction.class); } /** * Refund a transaction * * @param transactionId recurly transaction id * @param amount amount to refund, null for full refund */ public void refundTransaction(final String transactionId, @Nullable final BigDecimal amount) { String url = Transactions.TRANSACTIONS_RESOURCE + "/" + urlEncode(transactionId); if (amount != null) { url = url + "?amount_in_cents=" + (amount.intValue() * 100); } doDELETE(url); } /** * Get the subscriptions for a {@link Transaction}. *

* Returns subscriptions associated with a transaction * * @param transactionId recurly transaction id * @return Subscriptions on the transaction */ public Subscriptions getTransactionSubscriptions(final String transactionId) { return doGET(Transactions.TRANSACTIONS_RESOURCE + "/" + urlEncode(transactionId) + Subscriptions.SUBSCRIPTIONS_RESOURCE, Subscriptions.class, new QueryParams()); } /////////////////////////////////////////////////////////////////////////// // User invoices /** * Lookup an invoice *

* Returns the invoice given an integer id * * @deprecated Please switch to using a string for invoice ids * * @param invoiceId Recurly Invoice ID * @return the invoice */ @Deprecated public Invoice getInvoice(final Integer invoiceId) { return getInvoice(invoiceId.toString()); } /** * Lookup an invoice given an invoice id * *

* Returns the invoice given a string id. * The invoice may or may not have acountry code prefix (ex: IE1023). * For more information on invoicing and prefixes, see: * https://docs.recurly.com/docs/site-settings#section-invoice-prefixing * * @param invoiceId String Recurly Invoice ID * @return the invoice */ public Invoice getInvoice(final String invoiceId) { if (invoiceId == null || invoiceId.isEmpty()) throw new RuntimeException("invoiceId cannot be empty!"); return doGET(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId), Invoice.class); } /** * Update an invoice *

* Updates an existing invoice. * * @param invoiceId String Recurly Invoice ID * @return the updated invoice object on success, null otherwise */ public Invoice updateInvoice(final String invoiceId, final Invoice invoice) { return doPUT(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId), invoice, Invoice.class); } /** * Fetch invoice pdf *

* Returns the invoice pdf as an inputStream * * @deprecated Prefer using Invoice#getId() as the id param (which is a String) * * @param invoiceId Recurly Invoice ID * @return the invoice pdf as an inputStream */ @Deprecated public InputStream getInvoicePdf(final Integer invoiceId) { return getInvoicePdf(invoiceId.toString()); } /** * Fetch invoice pdf *

* Returns the invoice pdf as an inputStream * * @param invoiceId String Recurly Invoice ID * @return the invoice pdf as an inputStream */ public InputStream getInvoicePdf(final String invoiceId) { if (invoiceId == null || invoiceId.isEmpty()) throw new RuntimeException("invoiceId cannot be empty!"); return doGETPdf(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId)); } /** * Lookup all invoices *

* Returns all invoices on the site * * @return the invoices associated with this site on success, null otherwise */ public Invoices getInvoices() { return doGET(Invoices.INVOICES_RESOURCE, Invoices.class, new QueryParams()); } /** * Return all the invoices given query params *

* * @param params {@link QueryParams} * @return all invoices matching the query */ public Invoices getInvoices(final QueryParams params) { return doGET(Invoices.INVOICES_RESOURCE, Invoices.class, params); } /** * Return all the invoices given query params *

* * @param params {@link QueryParams} * @return the count of invoices matching the query */ public int getInvoicesCount(final QueryParams params) { HeaderGroup map = doHEAD(Invoices.INVOICES_RESOURCE, params); return Integer.parseInt(map.getFirstHeader(X_RECORDS_HEADER_NAME).getValue()); } /** * Return all the transactions on an invoice. Only use this endpoint * if you have more than 500 transactions on an invoice. *

* * @param invoiceId String Recurly Invoice ID * @return all the transactions on the invoice */ public Transactions getInvoiceTransactions(final String invoiceId) { return doGET(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId) + Transactions.TRANSACTIONS_RESOURCE, Transactions.class, new QueryParams()); } /** * Lookup an account's invoices *

* Returns the account's invoices * * @param accountCode recurly account id * @return the invoices associated with this account on success, null otherwise */ public Invoices getAccountInvoices(final String accountCode) { return doGET(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + Invoices.INVOICES_RESOURCE, Invoices.class, new QueryParams()); } /** * Lookup an invoice's original invoices (e.g. a refund invoice has original_invoices) *

* Returns the invoice's original invoices * * @param invoiceId the invoice id * @return the original invoices associated with this invoice on success. Throws RecurlyAPIError if not found */ public Invoices getOriginalInvoices(final String invoiceId) { return doGET(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId) + "/original_invoices", Invoices.class, new QueryParams()); } /** * Refund an invoice given an open amount *

* Returns the refunded invoice * * @deprecated Please use refundInvoice(String, InvoiceRefund) * * @param invoiceId The id of the invoice to refund * @param amountInCents The open amount to refund * @param method If credit line items exist on the invoice, this parameter specifies which refund method to use first * @return the refunded invoice */ @Deprecated public Invoice refundInvoice(final String invoiceId, final Integer amountInCents, final RefundMethod method) { final InvoiceRefund invoiceRefund = new InvoiceRefund(); invoiceRefund.setRefundMethod(method); invoiceRefund.setAmountInCents(amountInCents); return refundInvoice(invoiceId, invoiceRefund); } /** * Refund an invoice given some line items *

* Returns the refunded invoice * * @deprecated Please use refundInvoice(String, InvoiceRefund) * * @param invoiceId The id of the invoice to refund * @param lineItems The list of adjustment refund objects * @param method If credit line items exist on the invoice, this parameter specifies which refund method to use first * @return the refunded invoice */ @Deprecated public Invoice refundInvoice(final String invoiceId, List lineItems, final RefundMethod method) { final InvoiceRefund invoiceRefund = new InvoiceRefund(); invoiceRefund.setRefundMethod(method); invoiceRefund.setLineItems(lineItems); return refundInvoice(invoiceId, invoiceRefund); } /** * Refund an invoice given some options *

* Returns the refunded invoice * * @param invoiceId The id of the invoice to refund * @param refundOptions The options for the refund * @return the refunded invoice */ public Invoice refundInvoice(final String invoiceId, final InvoiceRefund refundOptions) { return doPOST(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId) + "/refund", refundOptions, Invoice.class); } /** * Lookup an account's shipping addresses *

* Returns the account's shipping addresses * * @param accountCode recurly account id * @return the shipping addresses associated with this account on success, null otherwise */ public ShippingAddresses getAccountShippingAddresses(final String accountCode) { return doGET(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + ShippingAddresses.SHIPPING_ADDRESSES_RESOURCE, ShippingAddresses.class, new QueryParams()); } /** * Get an existing shipping address *

* * @param accountCode recurly account id * @param shippingAddressId the shipping address id to fetch * @return the newly created shipping address on success */ public ShippingAddress getShippingAddress(final String accountCode, final long shippingAddressId) { return doGET(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + ShippingAddresses.SHIPPING_ADDRESSES_RESOURCE + "/" + shippingAddressId, ShippingAddress.class); } /** * Create a shipping address on an existing account *

* * @param accountCode recurly account id * @param shippingAddress the shipping address request data * @return the newly created shipping address on success */ public ShippingAddress createShippingAddress(final String accountCode, final ShippingAddress shippingAddress) { return doPOST(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + ShippingAddresses.SHIPPING_ADDRESSES_RESOURCE, shippingAddress, ShippingAddress.class); } /** * Update an existing shipping address *

* * @param accountCode recurly account id * @param shippingAddressId the shipping address id to update * @param shippingAddress the shipping address request data * @return the updated shipping address on success */ public ShippingAddress updateShippingAddress(final String accountCode, final long shippingAddressId, ShippingAddress shippingAddress) { return doPUT(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + ShippingAddresses.SHIPPING_ADDRESSES_RESOURCE + "/" + shippingAddressId, shippingAddress, ShippingAddress.class); } /** * Delete an existing shipping address *

* * @param accountCode recurly account id * @param shippingAddressId the shipping address id to delete */ public void deleteShippingAddress(final String accountCode, final long shippingAddressId) { doDELETE(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + ShippingAddresses.SHIPPING_ADDRESSES_RESOURCE + "/" + shippingAddressId); } /** * Lookup an account's invoices given query params *

* Returns the account's invoices * * @param accountCode recurly account id * @param state {@link InvoiceState} state of the invoices * @param params {@link QueryParams} * @return the invoices associated with this account on success, null otherwise */ public Invoices getAccountInvoices(final String accountCode, final InvoiceState state, final QueryParams params) { if (state != null) params.put("state", state.getType()); return doGET(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + Invoices.INVOICES_RESOURCE, Invoices.class, params); } /** * Post an invoice: invoice pending charges on an account *

* Returns an invoice collection * * @param accountCode * @return the invoice collection that was generated on success, null otherwise */ public InvoiceCollection postAccountInvoice(final String accountCode, final Invoice invoice) { return doPOST(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + Invoices.INVOICES_RESOURCE, invoice, InvoiceCollection.class); } /** * Mark an invoice as paid successfully - Recurly Enterprise Feature * * @deprecated Prefer using Invoice#getId() as the id param (which is a String) * * @param invoiceId Recurly Invoice ID */ @Deprecated public Invoice markInvoiceSuccessful(final Integer invoiceId) { return markInvoiceSuccessful(invoiceId.toString()); } /** * Mark an invoice as paid successfully - Recurly Enterprise Feature * * @param invoiceId String Recurly Invoice ID */ public Invoice markInvoiceSuccessful(final String invoiceId) { return doPUT(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId) + "/mark_successful", null, Invoice.class); } /** * Mark an invoice as failed collection * * @deprecated Prefer using Invoice#getId() as the id param (which is a String) * * @param invoiceId Recurly Invoice ID */ @Deprecated public InvoiceCollection markInvoiceFailed(final Integer invoiceId) { return markInvoiceFailed(invoiceId.toString()); } /** * Mark an invoice as failed collection * * @param invoiceId String Recurly Invoice ID */ public InvoiceCollection markInvoiceFailed(final String invoiceId) { return doPUT(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId) + "/mark_failed", null, InvoiceCollection.class); } /** * Force collect an invoice * * @param invoiceId String Recurly Invoice ID */ public Invoice forceCollectInvoice(final String invoiceId) { return doPUT(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId) + "/collect", null, Invoice.class); } /** * Force collect an invoice * * @param transactionType String The gateway transaction type. Currency accepts value "moto". * @param invoiceId String Recurly Invoice ID */ public Invoice forceCollectInvoice(final String invoiceId, final String transactionType) { Invoice request = new Invoice(); request.setTransactionType(transactionType); return doPUT(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId) + "/collect", request, Invoice.class); } /** * Force collect an invoice * * @param billingInfoUuid String The billing info uuid. * @param invoiceId String Recurly Invoice ID */ public Invoice forceCollectInvoiceWithBillingInfo(final String invoiceId, final String billingInfoUuid) { Invoice request = new Invoice(); request.setBillingInfoUuid(billingInfoUuid); return doPUT(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId) + "/collect", request, Invoice.class); } /** * Void Invoice * * @param invoiceId String Recurly Invoice ID */ public Invoice voidInvoice(final String invoiceId) { return doPUT(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId) + "/void", null, Invoice.class); } /** * Enter an offline payment for a manual invoice (beta) - Recurly Enterprise Feature * * @deprecated Prefer using Invoice#getId() as the id param (which is a String) * * @param invoiceId Recurly Invoice ID * @param payment The external payment */ @Deprecated public Transaction enterOfflinePayment(final Integer invoiceId, final Transaction payment) { return enterOfflinePayment(invoiceId.toString(), payment); } /** * Enter an offline payment for a manual invoice (beta) - Recurly Enterprise Feature * * @param invoiceId String Recurly Invoice ID * @param payment The external payment */ public Transaction enterOfflinePayment(final String invoiceId, final Transaction payment) { return doPOST(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId) + "/transactions", payment, Transaction.class); } /////////////////////////////////////////////////////////////////////////// /** * Create an Item's info *

* * @param item The item to create on recurly * @return the item object as identified by the passed in ID */ public Item createItem(final Item item) { return doPOST(Item.ITEMS_RESOURCE, item, Item.class); } /** * Update an Item's info *

* * @param item The Item to update on recurly * @return the updated item object */ public Item updateItem(final String itemCode, final Item item) { return doPUT(Item.ITEMS_RESOURCE + "/" + urlEncode(itemCode), item, Item.class); } /** * Get a Item's details *

* * @param itemCode recurly id of item * @return the item object as identified by the passed in ID */ public Item getItem(final String itemCode) { if (itemCode == null || itemCode.isEmpty()) throw new RuntimeException("itemCode cannot be empty!"); return doGET(Item.ITEMS_RESOURCE + "/" + urlEncode(itemCode), Item.class); } /** * Return all the items *

* * @return the item object as identified by the passed in ID */ public Items getItems() { return doGET(Items.ITEMS_RESOURCE, Items.class, new QueryParams()); } /** * Deletes a {@link Item} *

* * @param itemCode The {@link Item} object to delete. */ public void deleteItem(final String itemCode) { doDELETE(Item.ITEMS_RESOURCE + "/" + urlEncode(itemCode)); } /** * Reactivating a canceled item *

* Reactivate a canceled item. * * @param itemCode Item object * @return Item */ public Item reactivateItem(final String itemCode) { return doPUT(Item.ITEMS_RESOURCE + "/" + urlEncode(itemCode) + "/reactivate", null, Item.class); } /////////////////////////////////////////////////////////////////////////// /** * Create a Plan's info *

* * @param plan The plan to create on recurly * @return the plan object as identified by the passed in ID */ public Plan createPlan(final Plan plan) { return doPOST(Plan.PLANS_RESOURCE, plan, Plan.class); } /** * Update a Plan's info *

* * @param plan The plan to update on recurly * @return the updated plan object */ public Plan updatePlan(final Plan plan) { return doPUT(Plan.PLANS_RESOURCE + "/" + urlEncode(plan.getPlanCode()), plan, Plan.class); } /** * Get a Plan's details *

* * @param planCode recurly id of plan * @return the plan object as identified by the passed in ID */ public Plan getPlan(final String planCode) { if (planCode == null || planCode.isEmpty()) throw new RuntimeException("planCode cannot be empty!"); return doGET(Plan.PLANS_RESOURCE + "/" + urlEncode(planCode), Plan.class); } /** * Return all the plans *

* * @return the plan object as identified by the passed in ID */ public Plans getPlans() { return doGET(Plans.PLANS_RESOURCE, Plans.class, new QueryParams()); } /** * Return all the plans given query params *

* * @param params {@link QueryParams} * @return the plan object as identified by the passed in ID */ public Plans getPlans(final QueryParams params) { return doGET(Plans.PLANS_RESOURCE, Plans.class, params); } /** * Get number of Plans matching the query params * * @param params {@link QueryParams} * @return Integer on success, null otherwise */ public Integer getPlansCount(final QueryParams params) { HeaderGroup map = doHEAD(Plans.PLANS_RESOURCE, params); return Integer.parseInt(map.getFirstHeader(X_RECORDS_HEADER_NAME).getValue()); } /** * Deletes a {@link Plan} *

* * @param planCode The {@link Plan} object to delete. */ public void deletePlan(final String planCode) { doDELETE(Plan.PLANS_RESOURCE + "/" + urlEncode(planCode)); } /////////////////////////////////////////////////////////////////////////// /** * Create an AddOn to a Plan *

* * @param planCode The planCode of the {@link Plan } to create within recurly * @param addOn The {@link AddOn} to create within recurly * @return the {@link AddOn} object as identified by the passed in object */ public AddOn createPlanAddOn(final String planCode, final AddOn addOn) { return doPOST(Plan.PLANS_RESOURCE + "/" + urlEncode(planCode) + AddOn.ADDONS_RESOURCE, addOn, AddOn.class); } /** * Get an AddOn's details *

* * @param addOnCode recurly id of {@link AddOn} * @param planCode recurly id of {@link Plan} * @return the {@link AddOn} object as identified by the passed in plan and add-on IDs */ public AddOn getAddOn(final String planCode, final String addOnCode) { if (addOnCode == null || addOnCode.isEmpty()) throw new RuntimeException("addOnCode cannot be empty!"); return doGET(Plan.PLANS_RESOURCE + "/" + urlEncode(planCode) + AddOn.ADDONS_RESOURCE + "/" + addOnCode, AddOn.class); } /** * Return all the {@link AddOn} for a {@link Plan} *

* * @param planCode * @return the {@link AddOn} objects as identified by the passed plan ID */ public AddOns getAddOns(final String planCode) { return doGET(Plan.PLANS_RESOURCE + "/" + urlEncode(planCode) + AddOn.ADDONS_RESOURCE, AddOns.class, new QueryParams()); } /** * Return all the {@link AddOn} for a {@link Plan} *

* * @param planCode * @param params {@link QueryParams} * @return the {@link AddOn} objects as identified by the passed plan ID */ public AddOns getAddOns(final String planCode, final QueryParams params) { return doGET(Plan.PLANS_RESOURCE + "/" + urlEncode(planCode) + AddOn.ADDONS_RESOURCE, AddOns.class, params); } /** * Deletes an {@link AddOn} for a Plan *

* * @param planCode The {@link Plan} object. * @param addOnCode The {@link AddOn} object to delete. */ public void deleteAddOn(final String planCode, final String addOnCode) { doDELETE(Plan.PLANS_RESOURCE + "/" + urlEncode(planCode) + AddOn.ADDONS_RESOURCE + "/" + urlEncode(addOnCode)); } /** * Updates an {@link AddOn} for a Plan *

* * @param planCode The {@link Plan} object. * @param addOnCode The {@link AddOn} object to update. * @param addOn The updated {@link AddOn} data. * * @return the updated {@link AddOn} object. */ public AddOn updateAddOn(final String planCode, final String addOnCode, final AddOn addOn) { return doPUT(Plan.PLANS_RESOURCE + "/" + urlEncode(planCode) + AddOn.ADDONS_RESOURCE + "/" + urlEncode(addOnCode), addOn, AddOn.class); } /////////////////////////////////////////////////////////////////////////// /** * Create a {@link Coupon} *

* * @param coupon The coupon to create on recurly * @return the {@link Coupon} object */ public Coupon createCoupon(final Coupon coupon) { return doPOST(Coupon.COUPON_RESOURCE, coupon, Coupon.class); } /** * Get a Coupon *

* * @param couponCode The code for the {@link Coupon} * @return The {@link Coupon} object as identified by the passed in code */ public Coupon getCoupon(final String couponCode) { if (couponCode == null || couponCode.isEmpty()) throw new RuntimeException("couponCode cannot be empty!"); return doGET(Coupon.COUPON_RESOURCE + "/" + urlEncode(couponCode), Coupon.class); } /** * Delete a {@link Coupon} *

* * @param couponCode The code for the {@link Coupon} */ public void deleteCoupon(final String couponCode) { doDELETE(Coupon.COUPON_RESOURCE + "/" + urlEncode(couponCode)); } /** * Restore a {@link Coupon} by the coupon code and potentially update its editable fields *

* * @param couponCode The coupon code to restore * @param coupon A {@link Coupon} containing fields to update * @return */ public Coupon restoreCoupon(final String couponCode, final Coupon coupon) { return doPUT(Coupon.COUPON_RESOURCE + "/" + urlEncode(couponCode) + Coupon.RESTORE_RESOURCE, coupon, Coupon.class); } /////////////////////////////////////////////////////////////////////////// /** * Redeem a {@link Coupon} on an account. * * @param couponCode redeemed coupon id * @return the {@link Coupon} object */ public Redemption redeemCoupon(final String couponCode, final Redemption redemption) { return doPOST(Coupon.COUPON_RESOURCE + "/" + urlEncode(couponCode) + Redemption.REDEEM_RESOURCE, redemption, Redemption.class); } /** * Lookup the first coupon redemption on an account. * * @param accountCode recurly account id * @return the coupon redemption for this account on success, null otherwise */ public Redemption getCouponRedemptionByAccount(final String accountCode) { return doGET(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + Redemption.REDEMPTION_RESOURCE, Redemption.class); } /** * Lookup all coupon redemptions on an account. * * @param accountCode recurly account id * @return the coupon redemptions for this account on success, null otherwise */ public Redemptions getCouponRedemptionsByAccount(final String accountCode) { return doGET(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + Redemption.REDEMPTIONS_RESOURCE, Redemptions.class, new QueryParams()); } /** * Lookup all coupon redemptions on an account given query params. * * @param accountCode recurly account id * @param params {@link QueryParams} * @return the coupon redemptions for this account on success, null otherwise */ public Redemptions getCouponRedemptionsByAccount(final String accountCode, final QueryParams params) { return doGET(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + Redemption.REDEMPTIONS_RESOURCE, Redemptions.class, params); } /** * Lookup the first coupon redemption on an invoice. * * @deprecated Prefer using Invoice#getId() as the id param (which is a String) * * @param invoiceNumber invoice number * @return the coupon redemption for this invoice on success, null otherwise */ @Deprecated public Redemption getCouponRedemptionByInvoice(final Integer invoiceNumber) { return getCouponRedemptionByInvoice(invoiceNumber.toString()); } /** * Lookup the first coupon redemption on an invoice. * * @param invoiceId String invoice id * @return the coupon redemption for this invoice on success, null otherwise */ public Redemption getCouponRedemptionByInvoice(final String invoiceId) { return doGET(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId) + Redemption.REDEMPTION_RESOURCE, Redemption.class); } /** * Lookup all coupon redemptions on an invoice. * * @deprecated Prefer using Invoice#getId() as the id param (which is a String) * * @param invoiceNumber invoice number * @return the coupon redemptions for this invoice on success, null otherwise */ @Deprecated public Redemptions getCouponRedemptionsByInvoice(final Integer invoiceNumber) { return getCouponRedemptionsByInvoice(invoiceNumber.toString(), new QueryParams()); } /** * Lookup all coupon redemptions on an invoice. * * @param invoiceId String invoice id * @return the coupon redemptions for this invoice on success, null otherwise */ public Redemptions getCouponRedemptionsByInvoice(final String invoiceId) { return getCouponRedemptionsByInvoice(invoiceId, new QueryParams()); } /** * Lookup all coupon redemptions on an invoice given query params. * * @deprecated Prefer using Invoice#getId() as the id param (which is a String) * * @param invoiceNumber invoice number * @param params {@link QueryParams} * @return the coupon redemptions for this invoice on success, null otherwise */ @Deprecated public Redemptions getCouponRedemptionsByInvoice(final Integer invoiceNumber, final QueryParams params) { return getCouponRedemptionsByInvoice(invoiceNumber.toString(), params); } /** * Lookup all coupon redemptions on an invoice given query params. * * @param invoiceId String invoice id * @param params {@link QueryParams} * @return the coupon redemptions for this invoice on success, null otherwise */ public Redemptions getCouponRedemptionsByInvoice(final String invoiceId, final QueryParams params) { return doGET(Invoices.INVOICES_RESOURCE + "/" + urlEncode(invoiceId) + Redemption.REDEMPTIONS_RESOURCE, Redemptions.class, params); } /** * Lookup all coupon redemptions on a subscription given query params. * * @param subscriptionUuid String subscription uuid * @param params {@link QueryParams} * @return the coupon redemptions for this subscription on success, null otherwise */ public Redemptions getCouponRedemptionsBySubscription(final String subscriptionUuid, final QueryParams params) { return doGET(Subscription.SUBSCRIPTION_RESOURCE + "/" + urlEncode(subscriptionUuid) + Redemptions.REDEMPTIONS_RESOURCE, Redemptions.class, params); } /** * Deletes a coupon redemption from an account. * * @param accountCode recurly account id */ public void deleteCouponRedemption(final String accountCode) { doDELETE(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + Redemption.REDEMPTION_RESOURCE); } /** * Deletes a specific redemption. * * @param accountCode recurly account id * @param redemptionUuid recurly coupon redemption uuid */ public void deleteCouponRedemption(final String accountCode, final String redemptionUuid) { doDELETE(Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + Redemption.REDEMPTIONS_RESOURCE + "/" + urlEncode(redemptionUuid)); } /** * Generates unique codes for a bulk coupon. * * @param couponCode recurly coupon code (must have been created as type: bulk) * @param coupon A coupon with number of unique codes set */ public Coupons generateUniqueCodes(final String couponCode, final Coupon coupon) { Coupons coupons = doPOST(Coupon.COUPON_RESOURCE + "/" + urlEncode(couponCode) + Coupon.GENERATE_RESOURCE, coupon, Coupons.class); return coupons.getStart(); } /** * Lookup all unique codes for a bulk coupon given query params. * * @param couponCode String coupon code * @param params {@link QueryParams} * @return the unique coupon codes for the coupon code on success, null otherwise */ public Coupons getUniqueCouponCodes(final String couponCode, final QueryParams params) { return doGET(Coupon.COUPON_RESOURCE + "/" + urlEncode(couponCode) + Coupon.UNIQUE_CODES_RESOURCE, Coupons.class, params); } /////////////////////////////////////////////////////////////////////////// // // Recurly.js API // /////////////////////////////////////////////////////////////////////////// /** * Fetch Subscription *

* Returns subscription from a recurly.js token. * * @param recurlyToken token given by recurly.js * @return subscription object on success, null otherwise */ public Subscription fetchSubscription(final String recurlyToken) { return fetch(recurlyToken, Subscription.class); } /** * Fetch BillingInfo *

* Returns billing info from a recurly.js token. * * @param recurlyToken token given by recurly.js * @return billing info object on success, null otherwise */ public BillingInfo fetchBillingInfo(final String recurlyToken) { return fetch(recurlyToken, BillingInfo.class); } /** * Fetch Invoice *

* Returns invoice from a recurly.js token. * * @param recurlyToken token given by recurly.js * @return invoice object on success, null otherwise */ public Invoice fetchInvoice(final String recurlyToken) { return fetch(recurlyToken, Invoice.class); } /** * Get Gift Cards given query params *

* Returns information about all gift cards. * * @param params {@link QueryParams} * @return gitfcards object on success, null otherwise */ public GiftCards getGiftCards(final QueryParams params) { return doGET(GiftCards.GIFT_CARDS_RESOURCE, GiftCards.class, params); } /** * Get Gift Cards *

* Returns information about all gift cards. * * @return gitfcards object on success, null otherwise */ public GiftCards getGiftCards() { return doGET(GiftCards.GIFT_CARDS_RESOURCE, GiftCards.class, new QueryParams()); } /** * Get number of GiftCards matching the query params * * @param params {@link QueryParams} * @return Integer on success, null otherwise */ public Integer getGiftCardsCount(final QueryParams params) { HeaderGroup map = doHEAD(GiftCards.GIFT_CARDS_RESOURCE, params); return Integer.parseInt(map.getFirstHeader(X_RECORDS_HEADER_NAME).getValue()); } /** * Get a Gift Card *

* * @param giftCardId The id for the {@link GiftCard} * @return The {@link GiftCard} object as identified by the passed in id */ public GiftCard getGiftCard(final Long giftCardId) { return doGET(GiftCards.GIFT_CARDS_RESOURCE + "/" + giftCardId, GiftCard.class); } /** * Redeem a Gift Card *

* * @param redemptionCode The redemption code the {@link GiftCard} * @param accountCode The account code for the {@link Account} * @return The updated {@link GiftCard} object as identified by the passed in id */ public GiftCard redeemGiftCard(final String redemptionCode, final String accountCode) { final GiftCard.Redemption redemptionData = GiftCard.createRedemption(accountCode); final String url = GiftCards.GIFT_CARDS_RESOURCE + "/" + urlEncode(redemptionCode) + "/redeem"; return doPOST(url, redemptionData, GiftCard.class); } /** * Purchase a GiftCard *

* * @param giftCard The giftCard data * @return the giftCard object */ public GiftCard purchaseGiftCard(final GiftCard giftCard) { return doPOST(GiftCards.GIFT_CARDS_RESOURCE, giftCard, GiftCard.class); } /** * Preview a GiftCard *

* * @param giftCard The giftCard data * @return the giftCard object */ public GiftCard previewGiftCard(final GiftCard giftCard) { return doPOST(GiftCards.GIFT_CARDS_RESOURCE + "/preview", giftCard, GiftCard.class); } /** * Return all the MeasuredUnits *

* * @return the MeasuredUnits object as identified by the passed in ID */ public MeasuredUnits getMeasuredUnits() { return doGET(MeasuredUnits.MEASURED_UNITS_RESOURCE, MeasuredUnits.class, new QueryParams()); } /** * Create a MeasuredUnit's info *

* * @param measuredUnit The measuredUnit to create on recurly * @return the measuredUnit object as identified by the passed in ID */ public MeasuredUnit createMeasuredUnit(final MeasuredUnit measuredUnit) { return doPOST(MeasuredUnit.MEASURED_UNITS_RESOURCE, measuredUnit, MeasuredUnit.class); } /** * Purchases endpoint *

* https://developers.recurly.com/api-v2/v2.29/index.html#operation/createPurchase * * @param purchase The purchase data * @return The created invoice collection */ public InvoiceCollection purchase(final Purchase purchase) { return doPOST(Purchase.PURCHASES_ENDPOINT, purchase, InvoiceCollection.class); } /** * Purchases preview endpoint *

* https://developers.recurly.com/api-v2/v2.29/index.html#operation/previewPurchase * * @param purchase The purchase data * @return The preview invoice collection */ public InvoiceCollection previewPurchase(final Purchase purchase) { return doPOST(Purchase.PURCHASES_ENDPOINT + "/preview", purchase, InvoiceCollection.class); } /** * Purchases authorize endpoint. * * Generate an authorized invoice for the purchase. Runs validations + but does not run any transactions. This endpoint will create a + pending purchase that can be activated at a later time once payment + has been completed on an external source (e.g. Adyen's Hosted + Payment Pages). * *

* https://developers.recurly.com/api-v2/v2.29/index.html#operation/authorizePurchase * * @param purchase The purchase data * @return The authorized invoice collection */ public InvoiceCollection authorizePurchase(final Purchase purchase) { return doPOST(Purchase.PURCHASES_ENDPOINT + "/authorize", purchase, InvoiceCollection.class); } /** * Purchases pending endpoint. * * Use for Adyen HPP transaction requests. Runs validations + but does not run any transactions. * *

* https://developers.recurly.com/api-v2/v2.29/index.html#operation/pendingPurchase * * @param purchase The purchase data * @return The authorized invoice collection */ public InvoiceCollection pendingPurchase(final Purchase purchase) { return doPOST(Purchase.PURCHASES_ENDPOINT + "/pending", purchase, InvoiceCollection.class); } /** * Purchases capture endpoint. * * Capture an open Authorization request * *

* https://developers.recurly.com/api-v2/v2.29/index.html#operation/capturePurchase * * @param transactionUuid UUID of the transaction to cancel */ public InvoiceCollection capturePurchase(final String transactionUuid) { return doPOST(Purchase.PURCHASES_ENDPOINT + "/transaction-uuid-" + transactionUuid + "/capture", null, InvoiceCollection.class); } /** * Purchases cancel endpoint. * * Cancel an open Authorization request * *

* https://developers.recurly.com/api-v2/v2.29/index.html#operation/cancelPurchase * * @param transactionUuid UUID of the transaction to capture */ public InvoiceCollection cancelPurchase(final String transactionUuid) { return doPOST(Purchase.PURCHASES_ENDPOINT + "/transaction-uuid-" + transactionUuid + "/cancel", null, InvoiceCollection.class); } /** * Sets the acquisition details for an account *

* https://developers.recurly.com/api-v2/v2.29/index.html#operation/createAccountAcquisition * * @param accountCode The account's account code * @param acquisition The AccountAcquisition data * @return The created AccountAcquisition object */ public AccountAcquisition createAccountAcquisition(final String accountCode, final AccountAcquisition acquisition) { final String path = Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + AccountAcquisition.ACCOUNT_ACQUISITION_RESOURCE; return doPOST(path, acquisition, AccountAcquisition.class); } /** * Gets the acquisition details for an account *

* https://developers.recurly.com/api-v2/v2.29/index.html#operation/lookupAccountAcquisition * * @param accountCode The account's account code * @return The created AccountAcquisition object */ public AccountAcquisition getAccountAcquisition(final String accountCode) { final String path = Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + AccountAcquisition.ACCOUNT_ACQUISITION_RESOURCE; return doGET(path, AccountAcquisition.class); } /** * Updates the acquisition details for an account *

* https://developers.recurly.com/api-v2/v2.29/index.html#operation/updateAccountAcquisition * * @param accountCode The account's account code * @param acquisition The AccountAcquisition data * @return The created AccountAcquisition object */ public AccountAcquisition updateAccountAcquisition(final String accountCode, final AccountAcquisition acquisition) { final String path = Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + AccountAcquisition.ACCOUNT_ACQUISITION_RESOURCE; return doPUT(path, acquisition, AccountAcquisition.class); } /** * Clear the acquisition details for an account *

* https://developers.recurly.com/api-v2/v2.29/index.html#operation/clearAccountAcquisition * * @param accountCode The account's account code */ public void deleteAccountAcquisition(final String accountCode) { doDELETE(Account.ACCOUNT_RESOURCE + "/" + urlEncode(accountCode) + AccountAcquisition.ACCOUNT_ACQUISITION_RESOURCE); } /** * Get Credit Payments *

* Returns information about all credit payments. * * @return CreditPayments on success, null otherwise */ public CreditPayments getCreditPayments() { return doGET(CreditPayments.CREDIT_PAYMENTS_RESOURCE, CreditPayments.class, new QueryParams()); } /** * Get Credit Payments *

* Returns information about all credit payments. * * @param params {@link QueryParams} * @return CreditPayments on success, null otherwise */ public CreditPayments getCreditPayments(final QueryParams params) { return doGET(CreditPayments.CREDIT_PAYMENTS_RESOURCE, CreditPayments.class, params); } /** * Get Credit Payments for a given account *

* Returns information about all credit payments. * * @param accountCode The account code to filter * @param params {@link QueryParams} * @return CreditPayments on success, null otherwise */ public CreditPayments getCreditPayments(final String accountCode, final QueryParams params) { final String path = Accounts.ACCOUNTS_RESOURCE + "/" + urlEncode(accountCode) + CreditPayments.CREDIT_PAYMENTS_RESOURCE; return doGET(path, CreditPayments.class, params); } /** * Get Shipping Methods for the site *

* https://developers.recurly.com/api-v2/v2.29/index.html#operation/listShippingMethods * * @return ShippingMethods on success, null otherwise */ public ShippingMethods getShippingMethods() { return doGET(ShippingMethods.SHIPPING_METHODS_RESOURCE, ShippingMethods.class, new QueryParams()); } /** * Get Shipping Methods for the site *

* https://developers.recurly.com/api-v2/v2.29/index.html#operation/listShippingMethods * * @param params {@link QueryParams} * @return ShippingMethods on success, null otherwise */ public ShippingMethods getShippingMethods(final QueryParams params) { return doGET(ShippingMethods.SHIPPING_METHODS_RESOURCE, ShippingMethods.class, params); } /** * Look up a shipping method *

* https://developers.recurly.com/api-v2/v2.29/index.html#operation/lookupShippingMethod * * @param shippingMethodCode The code for the {@link ShippingMethod} * @return The {@link ShippingMethod} object as identified by the passed in code */ public ShippingMethod getShippingMethod(final String shippingMethodCode) { if (shippingMethodCode == null || shippingMethodCode.isEmpty()) throw new RuntimeException("shippingMethodCode cannot be empty!"); return doGET(ShippingMethod.SHIPPING_METHOD_RESOURCE + "/" + urlEncode(shippingMethodCode), ShippingMethod.class); } private T fetch(final String recurlyToken, final Class clazz) { return doGET(FETCH_RESOURCE + "/" + urlEncode(recurlyToken), clazz); } /////////////////////////////////////////////////////////////////////////// private InputStream doGETPdf(final String resource) { return doGETPdfWithFullURL(baseUrl + resource); } private T doGET(final String resource, final Class clazz) { return doGETWithFullURL(clazz, baseUrl + resource); } private T doGET(final String resource, final Class clazz, QueryParams params) { return doGETWithFullURL(clazz, constructUrl(resource, params)); } private String constructUrl(final String resource, QueryParams params) { return baseUrl + resource + params.toString(); } public T doGETWithFullURL(final Class clazz, final String url) { if (debug()) { log.info("Msg to Recurly API [GET] :: URL : {}", url); } return callRecurlySafeXmlContent(new HttpGet(url), clazz); } private InputStream doGETPdfWithFullURL(final String url) { if (debug()) { log.info(" [GET] :: URL : {}", url); } return callRecurlySafeGetPdf(url); } private InputStream callRecurlySafeGetPdf(String url) { CloseableHttpResponse response = null; InputStream pdfInputStream = null; try { final HttpGet builder = new HttpGet(url); clientRequestBuilderCommon(builder); builder.setHeader(HttpHeaders.ACCEPT, "application/pdf"); builder.setHeader(HttpHeaders.CONTENT_TYPE, "application/pdf"); response = client.execute(builder); if (response.getStatusLine().getStatusCode() != 200) { final RecurlyAPIError recurlyAPIError = RecurlyAPIError.buildFromResponse(response); throw new RecurlyAPIException(recurlyAPIError); } // Buffer the pdf in memory on purpose, because this was actually the behavior of AsyncHttpClient. final HttpEntity entity = response.getEntity(); if (entity != null) { final byte[] pdfBytes = EntityUtils.toByteArray(entity); pdfInputStream = new ByteArrayInputStream(pdfBytes); } } catch (IOException e) { log.error("Error retrieving response body", e); return null; } finally { closeResponse(response); } return pdfInputStream; } private T doPOST(final String resource, final RecurlyObject payload, final Class clazz) { final String xmlPayload; try { if (payload != null) { xmlPayload = RecurlyObject.sharedXmlMapper().writeValueAsString(payload); } else { xmlPayload = null; } if (debug()) { log.info("Msg to Recurly API [POST]:: URL : {}", baseUrl + resource); log.info("Payload for [POST]:: {}", xmlPayload); } } catch (IOException e) { log.warn("Unable to serialize {} object as XML: {}", clazz.getName(), payload.toString()); return null; } final HttpPost builder = new HttpPost(baseUrl + resource); if (xmlPayload != null) { builder.setEntity(new StringEntity(xmlPayload, ContentType.APPLICATION_XML.withCharset(Charsets.UTF_8))); } return callRecurlySafeXmlContent(builder, clazz); } private T doPUT(final String resource, final RecurlyObject payload, final Class clazz) { return doPUT(resource, payload, clazz, new QueryParams()); } private T doPUT(final String resource, final RecurlyObject payload, final Class clazz, final QueryParams params) { final String xmlPayload; try { if (payload != null) { xmlPayload = RecurlyObject.sharedXmlMapper().writeValueAsString(payload); } else { xmlPayload = null; } if (debug()) { log.info("Msg to Recurly API [PUT]:: URL : {}", baseUrl + resource); log.info("Payload for [PUT]:: {}", xmlPayload); } } catch (IOException e) { log.warn("Unable to serialize {} object as XML: {}", clazz.getName(), payload.toString()); return null; } final HttpPut builder = new HttpPut(constructUrl(resource, params)); if (xmlPayload != null) { builder.setEntity(new StringEntity(xmlPayload, ContentType.APPLICATION_XML.withCharset(Charsets.UTF_8))); } return callRecurlySafeXmlContent(builder, clazz); } private HeaderGroup doHEAD(final String resource, QueryParams params) { if (params == null) { params = new QueryParams(); } final String url = constructUrl(resource, params); if (debug()) { log.info("Msg to Recurly API [HEAD]:: URL : {}", url); } return callRecurlyNoContent(new HttpHead(url)); } private void doDELETE(final String resource) { callRecurlySafeXmlContent(new HttpDelete(baseUrl + resource), null); } private HeaderGroup callRecurlyNoContent(final HttpRequestBase builder) { clientRequestBuilderCommon(builder); builder.setHeader(HttpHeaders.ACCEPT, "application/xml"); builder.setHeader(HttpHeaders.CONTENT_TYPE, "application/xml; charset=utf-8"); CloseableHttpResponse response = null; try { response = client.execute(builder); // Copy all the headers into a HeaderGroup, which will handle case insensitive headers for us final HeaderGroup headerGroup = new HeaderGroup(); for (Header header : response.getAllHeaders()) { headerGroup.addHeader(header); } return headerGroup; } catch (IOException e) { log.error("Execution error", e); return null; } finally { closeResponse(response); } } private T callRecurlySafeXmlContent(final HttpRequestBase builder, @Nullable final Class clazz) { try { return callRecurlyXmlContent(builder, clazz); } catch (IOException e) { if (e instanceof ConnectException || e instanceof NoHttpResponseException || e instanceof ConnectTimeoutException || e instanceof SSLException) { // See https://github.com/killbilling/recurly-java-library/issues/185 throw new ConnectionErrorException(e); } log.warn("Error while calling Recurly", e); return null; } // No need to extract TransactionErrorException since it's already a RuntimeException } private T callRecurlyXmlContent(final HttpRequestBase builder, @Nullable final Class clazz) throws IOException { clientRequestBuilderCommon(builder); builder.setHeader(HttpHeaders.ACCEPT, "application/xml"); builder.setHeader(HttpHeaders.CONTENT_TYPE, "application/xml; charset=utf-8"); CloseableHttpResponse response = null; try { response = client.execute(builder); final String payload = convertEntityToString(response.getEntity()); if (debug()) { log.info("Msg from Recurly API :: {}", payload); } // Handle errors payload if (response.getStatusLine().getStatusCode() >= 300) { log.warn("Recurly error whilst calling: {}\n{}", builder.getURI(), payload); log.warn("Error status code: {}\n", response.getStatusLine().getStatusCode()); RecurlyAPIError recurlyError = RecurlyAPIError.buildFromResponse(response); if (response.getStatusLine().getStatusCode() == 422) { // 422 is returned for transaction errors (see https://developers.recurly.com/pages/api-v2/transaction-errors.html) // as well as bad input payloads final Errors errors; try { errors = RecurlyObject.sharedXmlMapper().readValue(payload, Errors.class); } catch (Exception e) { log.warn("Unable to extract error", e); return null; } // Sometimes a single `Error` response is returned rather than `Errors`. // In this case, all fields will be null. if (errors == null || ( errors.getRecurlyErrors() == null && errors.getTransaction() == null && errors.getTransactionError() == null )) { recurlyError = RecurlyAPIError.buildFromXml(RecurlyObject.sharedXmlMapper(), payload, response); throw new RecurlyAPIException(recurlyError); } throw new TransactionErrorException(errors); } else if (response.getStatusLine().getStatusCode() == 401) { recurlyError.setSymbol("unauthorized"); recurlyError.setDescription("We could not authenticate your request. Either your subdomain and private key are not set or incorrect"); throw new RecurlyAPIException(recurlyError); } else { try { recurlyError = RecurlyAPIError.buildFromXml(RecurlyObject.sharedXmlMapper(), payload, response); } catch (Exception e) { log.debug("Unable to extract error", e); } throw new RecurlyAPIException(recurlyError); } } if (clazz == null) { return null; } final Header locationHeader = response.getFirstHeader(HttpHeaders.LOCATION); final String location = locationHeader == null ? null : locationHeader.getValue(); if (clazz == Coupons.class && location != null && !location.isEmpty()) { final RecurlyObjects recurlyObjects = new Coupons(); recurlyObjects.setRecurlyClient(this); recurlyObjects.setStartUrl(location); return (T) recurlyObjects; } final T obj = RecurlyObject.sharedXmlMapper().readValue(payload, clazz); if (obj instanceof RecurlyObject) { ((RecurlyObject) obj).setRecurlyClient(this); } else if (obj instanceof RecurlyObjects) { final RecurlyObjects recurlyObjects = (RecurlyObjects) obj; recurlyObjects.setRecurlyClient(this); // Set the RecurlyClient on all objects for later use for (final Object object : recurlyObjects) { ((RecurlyObject) object).setRecurlyClient(this); } // Set links for pagination final Header linkHeader = response.getFirstHeader(LINK_HEADER_NAME); if (linkHeader != null) { final String[] links = PaginationUtils.getLinks(linkHeader.getValue()); recurlyObjects.setStartUrl(links[0]); recurlyObjects.setNextUrl(links[1]); } } // Save value of rate limit remaining header Header rateLimitRemainingString = response.getFirstHeader(X_RATELIMIT_REMAINING_HEADER_NAME); if (rateLimitRemainingString != null) rateLimitRemaining = Integer.parseInt(rateLimitRemainingString.getValue()); return obj; } finally { closeResponse(response); } } private void clientRequestBuilderCommon(HttpRequestBase requestBuilder) { validateHost(requestBuilder.getURI()); requestBuilder.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + key); requestBuilder.setHeader("X-Api-Version", RECURLY_API_VERSION); requestBuilder.setHeader(HttpHeaders.USER_AGENT, userAgent); requestBuilder.setHeader(HttpHeaders.ACCEPT_LANGUAGE, acceptLanguage); } private String convertEntityToString(HttpEntity entity) { if (entity == null) { return ""; } final String entityString; try { entityString = EntityUtils.toString(entity, Charsets.UTF_8); } catch (ParseException | IOException e) { return ""; } return entityString == null ? "" : entityString; } private void closeResponse(final CloseableHttpResponse response) { if (response != null) { try { response.close(); } catch (IOException e) { log.warn("Failed to close {}: {}", response.getClass().getSimpleName(), e.getLocalizedMessage()); } } } protected CloseableHttpClient createHttpClient() throws KeyManagementException, NoSuchAlgorithmException { // Don't limit the number of connections per host // See https://github.com/ning/async-http-client/issues/issue/28 final HttpClientBuilder httpClientBuilder = HttpClients.custom() .disableCookieManagement() // We don't need cookies /* * The following limits are not quite truly unlimited, but in practice they * should be more than enough. */ .setMaxConnPerRoute(256) // default is 2 .setMaxConnTotal(512) // default is 20 // Use the default timeouts from AHC .setDefaultRequestConfig(RequestConfig.custom() .setConnectTimeout(5000).setSocketTimeout(60000).build()) .setSSLContext(SslUtils.getInstance().getSSLContext()); return httpClientBuilder.build(); } private void validateHost(URI uri) { String host = uri.getHost(); // Remove the subdomain from the host host = host.substring(host.indexOf(".")+1); if (!validHosts.contains(host)) { String exc = String.format(Locale.ROOT, "Attempted to make call to %s instead of Recurly", host); throw new RuntimeException(exc); } } @VisibleForTesting String getUserAgent() { return userAgent; } private static String buildUserAgent() { final String defaultVersion = "0.0.0"; final String defaultJavaVersion = "0.0.0"; try { final Properties gitRepositoryState = new Properties(); final URL resourceURL = MoreObjects.firstNonNull( Thread.currentThread().getContextClassLoader(), RecurlyClient.class.getClassLoader()).getResource(GIT_PROPERTIES_FILE); try (Reader reader = new InputStreamReader(resourceURL.openStream(), Charsets.UTF_8)) { gitRepositoryState.load(reader); } final String version = MoreObjects.firstNonNull(getVersionFromGitRepositoryState(gitRepositoryState), defaultVersion); final String javaVersion = MoreObjects.firstNonNull(StandardSystemProperty.JAVA_VERSION.value(), defaultJavaVersion); return String.format(Locale.ROOT, "KillBill/%s; %s", version, javaVersion); } catch (final Exception e) { return String.format(Locale.ROOT, "KillBill/%s; %s", defaultVersion, defaultJavaVersion); } } @VisibleForTesting static String getVersionFromGitRepositoryState(final Properties gitRepositoryState) { final String gitDescribe = gitRepositoryState.getProperty(GIT_COMMIT_ID_DESCRIBE_SHORT); if (gitDescribe == null) { return null; } final Matcher matcher = TAG_FROM_GIT_DESCRIBE_PATTERN.matcher(gitDescribe); return matcher.find() ? matcher.group(1) : null; } /** * RFC 3986 URL encoding. The vanilla {@link URLEncoder} does not work since * Recurly does not decode '+' back to ' '. */ private static String urlEncode(String s) { return new String(URLCodec.encodeUrl(RFC_3986_SAFE_CHARS, s.getBytes(Charsets.UTF_8)), Charsets.UTF_8); } /** * Class that holds the cached user agent. This class exists so * {@link RecurlyClient#buildUserAgent()} will only run when the first instance * of {@link RecurlyClient} is created. */ private static class UserAgentHolder { private static final String userAgent = buildUserAgent(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy