/*
* Copyright 2014 serso aka se.solovyev
*
* Licensed 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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Contact details
*
* Email: se.solovyev@gmail.com
* Site: http://se.solovyev.org
*/
package org.solovyev.android.checkout;
import org.json.JSONException;
import org.json.JSONObject;
import android.text.TextUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
* SKU object as described <a href="http://developer.android.com/google/play/billing/billing_reference.html#product-details-table">here</a>
*/
@Immutable
public final class Sku {
@Nonnull
public final Id id;
// formatted price of the item, including its currency sign. The price does not include tax.
// See #detailedPrice for parsed values
@Nonnull
public final String price;
@Nonnull
public final Price detailedPrice;
// title of the product
@Nonnull
public final String title;
// description of the product
@Nonnull
public final String description;
@Nullable
private String mDisplayTitle;
public Sku(@Nonnull String product, @Nonnull String code, @Nonnull String price, @Nonnull Price detailedPrice, @Nonnull String title, @Nonnull String description) {
this.id = new Id(product, code);
this.price = price;
this.detailedPrice = detailedPrice;
this.title = title;
this.description = description;
}
Sku(@Nonnull String json, @Nonnull String product) throws JSONException {
final JSONObject object = new JSONObject(json);
id = new Id(product, object.getString("productId"));
price = object.getString("price");
detailedPrice = Price.fromJson(object);
title = object.getString("title");
description = object.optString("description");
}
@Nonnull
static Sku fromJson(@Nonnull String json, @Nonnull String product) throws JSONException {
return new Sku(json, product);
}
@Nonnull
String toJson() throws JSONException {
return toJsonObject().toString();
}
@Nonnull
private JSONObject toJsonObject() throws JSONException {
final JSONObject json = new JSONObject();
json.put("productId", id.code);
json.put("price", price);
json.put("price_amount_micros", detailedPrice.amount);
json.put("price_currency_code", detailedPrice.currency);
json.put("title", title);
json.put("description", description);
return json;
}
@Override
public String toString() {
return id + "{" + getDisplayTitle() + ", " + price + "}";
}
/**
* @return {@link #title} without application name in it (the last group of characters
* surrounded by brackets is removed)
*/
@Nonnull
public String getDisplayTitle() {
if (mDisplayTitle == null) {
mDisplayTitle = makeDisplayTitle(title);
}
return mDisplayTitle;
}
@Nonnull
private static String makeDisplayTitle(String title) {
if (TextUtils.isEmpty(title)) {
return "";
}
final char lastChar = title.charAt(title.length() - 1);
if (lastChar != ')') {
return title;
}
final int i = indexOfAppName(title);
if (i > 0) {
return title.substring(0, i).trim();
}
return title;
}
/**
* This method assumes that SKU's title has the following format: "$title$ ($app_name$)", where
* $title$ is the SKU's name and $title$ is the application name.
* @param title SKU's title
* @return position in the title where application name begins
*/
private static int indexOfAppName(String title) {
int depth = 0;
for (int i = title.length() - 1; i >= 0; i--) {
final char c = title.charAt(i);
if (c == ')') {
depth++;
} else if (c == '(') {
depth--;
}
if (depth == 0) {
return i;
}
}
return -1;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Sku that = (Sku) o;
return id.equals(that.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
public boolean isInApp() {
return id.isInApp();
}
public boolean isSubscription() {
return id.isSubscription();
}
public static final class Id {
// either “inapp” for in-apps or "subs" for subscriptions.
public final String product;
// SKU code
public final String code;
public Id(String product, String code) {
this.product = product;
this.code = code;
}
public boolean isInApp() {
return product.equals(ProductTypes.IN_APP);
}
public boolean isSubscription() {
return product.equals(ProductTypes.SUBSCRIPTION);
}
@Override
public String toString() {
return product + "/" + code;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Id that = (Id) o;
if (!product.equals(that.product)) return false;
return code.equals(that.code);
}
@Override
public int hashCode() {
int result = product.hashCode();
result = 31 * result + code.hashCode();
return result;
}
}
/**
* Contains detailed information about SKU's price as described <a
* href="http://developer.android.com/google/play/billing/billing_reference.html#getSkuDetails">here</a>
*/
public static final class Price {
@Nonnull
public static final Price EMPTY = new Price(0, "");
// price in micro-units, where 1,000,000 micro-units equal one unit of the currency.
// For example, if price is "€7.99", price_amount_micros is "7990000"
public final long amount;
// ISO 4217 currency code for price. For example, if price is specified in British pounds
// sterling, price_currency_code is "GBP"
@Nonnull
public final String currency;
public Price(long amount, @Nonnull String currency) {
this.amount = amount;
this.currency = currency;
}
@Nonnull
private static Price fromJson(@Nonnull JSONObject json) throws JSONException {
final long amount = json.optLong("price_amount_micros");
final String currency = json.optString("price_currency_code");
if (amount == 0 || TextUtils.isEmpty(currency)) {
return EMPTY;
} else {
return new Price(amount, currency);
}
}
/**
* @return true if both {@link #amount} and {@link #currency} are valid (non empty)
*/
public boolean isValid() {
return amount > 0 && !TextUtils.isEmpty(currency);
}
@Override
public String toString() {
return currency + amount;
}
}
}