/* vim: set ts=2 et sw=2 cindent fo=qroca: */
package com.globant.google.mendoza.malbec;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.FieldPosition;
import java.util.Locale;
import javax.xml.bind.JAXBElement;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.globant.google.mendoza.malbec.schema._2.BackorderItemsRequest;
import com.globant.google.mendoza.malbec.schema._2.CancelItemsRequest;
import com.globant.google.mendoza.malbec.schema._2.ItemId;
import com.globant.google.mendoza.malbec.schema._2.ItemShippingInformation;
import com.globant.google.mendoza.malbec.schema._2.
OrderShippingInformationResponse;
import com.globant.google.mendoza.malbec.schema._2.
ResetItemsShippingInformationRequest;
import com.globant.google.mendoza.malbec.schema._2.ReturnItemsRequest;
import com.globant.google.mendoza.malbec.schema._2.TrackingData;
import com.globant.google.mendoza.malbec.schema._2.
ShipItemsRequest.ItemShippingInformationList;
import com.globant.google.mendoza.malbec.transport.Transport;
/** A GBuy order.
*/
// TODO Replace the wired html to objects and then use the converter to get the
// xml from it.
// TODO Replace String + with StringBuffer append.
public final class Order {
/** The response xml namespace.
*/
private static final String NAMESPACE = "http://checkout.google.com/schema/2";
/** The class logger.
*/
private static Log log = LogFactory.getLog(Order.class);
/** The order number.
*/
private String orderNumber = null;
/** The transport implementation.
*/
private Transport transport = null;
/** The possible values for the carrier.
*/
public enum Carrier {
/** The DHL carrier. */
DHL,
/** Federal express carrier. */
FedEx,
/** The UPS carrier. */
UPS,
/** The USPS carrier. */
USPS,
/** Another unspecified carrier. */
Other
}
/** Builds an order.
*
* @param theTransport The transport implementation. It cannot be null.
*
* @param number The GBuy order number. Cannot be null.
*/
public Order(final Transport theTransport, final String number) {
if (theTransport == null) {
throw new IllegalArgumentException("the transport cannot be null");
}
if (number == null) {
throw new IllegalArgumentException(
"the order number cannot be null");
}
orderNumber = number;
transport = theTransport;
}
/** Gets the GBuy order number.
*
* @return Returns the order number.
*/
public String getNumber() {
return orderNumber;
}
/** Attempts to charge the order for the full order amount.
*
* Possible invalid state errors for charge-order: the order is not in a
* financial order state that can be charged.
*/
public void charge() {
log.trace("Entering charge");
String xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<charge-order xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\"/>\n";
send(xmlMessage);
log.trace("Leaving charge");
}
/** Attempts to charge the order for a partial order amount.
*
* Possible invalid argument errors for charge-order: requested amount is
* greater than remaining available amount, requested amount is zero or
* negative, requested amount has too many fractional digits.
*
* Possible invalid state errors for charge-order: the order is not in a
* financial order state that can be charged.
*
* @todo Add the currency (USD) as a parameter to charge.
*
* @param amount The amount to charge the order.
*/
public void charge(final BigDecimal amount) {
log.trace("Entering charge");
StringBuffer formattedAmount = new StringBuffer();
DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US);
DecimalFormat format = new DecimalFormat("0.00", symbols);
format.format(amount, formattedAmount, new FieldPosition(0));
String xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<charge-order xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\">\n"
+ " <amount currency=\"USD\">" + formattedAmount.toString()
+ "</amount>\n" + "</charge-order>\n";
send(xmlMessage);
log.trace("Leaving charge");
}
/** Refunds the buyer the full amount of the order.
*
* Possible invalid state errors for refund-order: the order is not in a
* financial order state that can be refunded.
*
* @param reason The reason the refund is made. It cannot be null.
*
* @param comment An additional comment. It can be null, in with case no
* comment is sent to the buyer.
*/
public void refund(final String reason, final String comment) {
log.trace("Entering refund");
if (reason == null) {
throw new IllegalArgumentException("the reason cannot be null");
}
String xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<refund-order xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\">\n";
if (comment != null) {
xmlMessage += " <comment>" + comment + "</comment>\n";
}
xmlMessage += " <reason>" + reason + "</reason>\n"
+ "</refund-order>\n";
send(xmlMessage);
log.trace("Leaving refund");
}
/** Refunds the buyer a partial amount of the order.
*
* Possible invalid argument errors for refund-order: requested amount is
* greater than remaining available amount, requested amount is zero or
* negative, requested amount has too many fractional digits.
*
* Possible invalid state errors for refund-order: the order is not in a
* financial order state that can be refunded.
*
* @param amount The amount to refund.
*
* @param reason The reason the refund is made. It cannot be null.
*
* @param comment An additional comment. It can be null, in with case no
* comment is sent to the buyer.
*/
public void refund(final BigDecimal amount, final String reason,
final String comment) {
log.trace("Entering refund");
if (reason == null) {
throw new IllegalArgumentException("the reason cannot be null");
}
StringBuffer formattedAmount = new StringBuffer();
DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US);
DecimalFormat format = new DecimalFormat("0.00", symbols);
format.format(amount, formattedAmount, new FieldPosition(0));
String xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<refund-order xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\">\n"
+ " <amount currency=\"USD\">" + formattedAmount.toString()
+ "</amount>\n";
if (comment != null) {
xmlMessage += " <comment>" + comment + "</comment>\n";
}
xmlMessage += " <reason>" + reason + "</reason>\n"
+ "</refund-order>\n";
send(xmlMessage);
log.trace("Leaving refund");
}
/** Cancels the order. If the order has been charged, you must refund it
* before you can cancel it.
*
* Possible invalid state errors for cancel-order: the order is not in a
* financial order state that can be cancelled.
*
* @param reason The reason the order was cancelled. Cannot be null.
*
* @param comment An optional comment. Note that this comment will be visible
* to the buyer in the order history. It can be null.
*/
public void cancel(final String reason, final String comment) {
log.trace("Entering cancel");
if (reason == null) {
throw new IllegalArgumentException("the reason cannot be null");
}
String xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<cancel-order xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\">\n";
if (comment != null) {
xmlMessage += " <comment>" + comment + "</comment>\n";
}
xmlMessage += " <reason>" + reason + "</reason>\n"
+ "</cancel-order>\n";
send(xmlMessage);
log.trace("Leaving cancel");
}
/** Notifies the buyer that the order is being processed.
*
* Adds a note to the buyer's inbox that says you're processing the order.
*/
public void process() {
log.trace("Entering process");
String xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<process-order xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\"/>\n";
send(xmlMessage);
log.trace("Leaving process");
}
/** Notifies the buyer that the order has been shipped.
*
* @param sendEmail States if GBuy must send a mail to the buyer (true) or
* not (false).
*/
public void deliver(final boolean sendEmail) {
log.trace("Entering deliver");
String xmlMessage;
xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<deliver-order xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\">\n";
if (!sendEmail) {
xmlMessage += " <send-email>false</send-email>\n";
}
xmlMessage += "</deliver-order>\n";
send(xmlMessage);
log.trace("Leaving deliver");
}
/** Notifies the buyer that the order has been shipped.
*
* @param carrier The name of the carrier. See the Carrier enumerator for
* possible values. Cannot be null.
*
* @param trackingNumber The assigned tracking number for this order
* shipment. It cannot be null.
*
* @param sendEmail States if GBuy must send a mail to the buyer (true) or
* not (false).
*/
public void deliver(final Carrier carrier, final String trackingNumber,
final boolean sendEmail) {
log.trace("Entering deliver");
if (carrier == null) {
throw new IllegalArgumentException("the carrier cannot be null");
}
if (trackingNumber == null) {
throw new IllegalArgumentException(
"the tracking number cannot be null");
}
String xmlMessage;
xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<deliver-order xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\">\n"
+ " <tracking-data>\n" + " <carrier>" + carrier
+ "</carrier>\n" + " <tracking-number>" + trackingNumber
+ "</tracking-number>\n" + " </tracking-data>\n";
if (!sendEmail) {
xmlMessage += " <send-email>false</send-email>\n";
}
xmlMessage += "</deliver-order>\n";
send(xmlMessage);
log.trace("Leaving deliver");
}
/** Adds tracking data to a shipped order.
*
* Add a shipping tracking number to this order. Be sure to do this when you
* have tracking information, as stated in GBuy Program Policies and
* Guidelines, section 1d.
*
* @param carrier The name of the carrier. See the Carrier enumerator for
* possible values. Cannot be null.
*
* @param trackingNumber The assigned tracking number for this order
* shipment. It cannot be null.
*/
public void addTrackingData(final Carrier carrier,
final String trackingNumber) {
log.trace("Entering addTrackingData");
if (carrier == null) {
throw new IllegalArgumentException("the carrier cannot be null");
}
if (trackingNumber == null) {
throw new IllegalArgumentException(
"the tracking number cannot be null");
}
String xmlMessage;
xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<add-tracking-data xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\">\n"
+ " <tracking-data>\n" + " <carrier>" + carrier
+ "</carrier>\n" + " <tracking-number>" + trackingNumber
+ "</tracking-number>\n" + " </tracking-data>\n"
+ "</add-tracking-data>\n";
send(xmlMessage);
log.trace("Leaving addTrackingData");
}
/** Adds the merchant order number to this order.
*
* @param merchantOrderNumber The merchant order number to associate to this
* order. It cannot be null.
*/
public void addMerchantOrderNumber(final String merchantOrderNumber) {
log.trace("Entering addMerchantOrderNumber");
if (merchantOrderNumber == null) {
throw new IllegalArgumentException(
"the merchant order number cannot be" + " null");
}
String xmlMessage;
xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<add-merchant-order-number xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\">\n"
+ " <merchant-order-number>" + merchantOrderNumber
+ "</merchant-order-number>\n"
+ "</add-merchant-order-number>\n";
send(xmlMessage);
log.trace("Leaving addMerchantOrderNumber");
}
/** Place a message in the buyer's account and optionally send the message
* via email.
*
* @param message The message to send to the buyer. It cannot be null.
*
* @param sendEmail If true, this also sends a mail to the buyer.
*/
public void sendBuyerMessage(final String message, final boolean sendEmail) {
log.trace("Entering sendBuyerMessage");
if (message == null) {
throw new IllegalArgumentException("the message cannot be null");
}
String xmlMessage;
xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<send-buyer-message xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\">\n";
if (sendEmail) {
xmlMessage += " <send-email>true</send-email>\n";
}
xmlMessage += " <message>" + message + "</message>\n"
+ "</send-buyer-message>\n";
send(xmlMessage);
log.trace("Leaving sendBuyerMessage");
}
/** Request the order shipping information to Checkout.
* @return The shipping information response.
*/
public OrderShippingInformationResponse getOrderShippingInformation() {
log.trace("Entering getOrderShipppingInformation");
String xmlMessage;
xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<get-order-shipping-information xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\"/>\n";
Object response = sendAndReceive(xmlMessage);
if (!(response instanceof OrderShippingInformationResponse)) {
throw new RuntimeException(
"Unexpected response after sending a"
+ " get-order-shipping-information message");
}
log.trace("Leaving getOrderShipppingInformation");
return (OrderShippingInformationResponse) response;
}
/** Restores the given order to your Merchant Account inbox.
*/
public void unArchive() {
log.trace("Entering unArchive");
String xmlMessage;
xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<unarchive-order xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\"/>\n";
send(xmlMessage);
log.trace("Leaving unArchive");
}
/** Adds information of the shipment in a per item way.
*
* @param itemInformation The shipping information of every item, includes
* item-id, carrier and tracking-number. Cannot be neither null nor empty
*
* @param sendEmail States if GBuy must send a mail to the buyer (true) or
* not (false).
*/
public void shipItems(final ItemShippingInformationList itemInformation,
final boolean sendEmail) {
log.trace("Entering shipItems");
if (itemInformation == null) {
throw new IllegalArgumentException("the itemInformation cannot be null");
}
if (itemInformation.getItemShippingInformation().isEmpty()) {
throw new IllegalArgumentException(
"the itemInformation list cannot be empty");
}
String xmlMessage;
xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<ship-items xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\">\n"
+ " <item-shipping-information-list>\n";
for (ItemShippingInformation item
: itemInformation.getItemShippingInformation()) {
xmlMessage += " <item-shipping-information>\n"
+ " <item-id>\n";
if (item.getItemId().getMerchantItemId() != null) {
xmlMessage += " <merchant-item-id>"
+ item.getItemId().getMerchantItemId()
+ "</merchant-item-id>\n";
}
if (item.getItemId().getGoogleItemId() != null) {
xmlMessage += "<google-item-id>" + item.getItemId().getGoogleItemId()
+ "</google-item-id>\n";
}
xmlMessage += "</item-id>\n"
+ "<tracking-data-list>\n";
for (TrackingData trackingData
: item.getTrackingDataList().getTrackingData()) {
xmlMessage += "<tracking-data>\n"
+ "<carrier>" + trackingData.getCarrier() + "</carrier>\n"
+ "<tracking-number>" + trackingData.getTrackingNumber()
+ "</tracking-number>\n"
+ "</tracking-data>\n";
}
xmlMessage += "</tracking-data-list>\n" + "</item-shipping-information>\n";
}
xmlMessage += " </item-shipping-information-list>\n";
if (!sendEmail) {
xmlMessage += " <send-email>false</send-email>\n";
}
xmlMessage += "</ship-items>\n";
send(xmlMessage);
log.trace("Leaving shipItems");
}
/** Cancel the indicated items.
*
* @param itemIds The item ids that needs to be cancel.
*
* @param sendEmail States if GBuy must send a mail to the buyer (true) or
* not (false).
*/
public void cancelItems(final CancelItemsRequest.ItemIds itemIds,
final boolean sendEmail) {
log.trace("Entering cancelItems");
if (itemIds == null) {
throw new IllegalArgumentException("the itemInformation cannot be null");
}
if (itemIds.getItemId().isEmpty()) {
throw new IllegalArgumentException(
"the itemIds list cannot be empty");
}
String xmlMessage;
xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<cancel-items xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\">\n"
+ " <item-ids>\n";
for (ItemId item : itemIds.getItemId()) {
xmlMessage += " <item-id>\n";
if (item.getMerchantItemId() != null) {
xmlMessage += " <merchant-item-id>"
+ item.getMerchantItemId()
+ "</merchant-item-id>\n";
}
if (item.getGoogleItemId() != null) {
xmlMessage += "<google-item-id>" + item.getGoogleItemId()
+ "</google-item-id>\n";
}
xmlMessage += "</item-id>\n";
}
xmlMessage += "</item-ids>\n";
if (!sendEmail) {
xmlMessage += " <send-email>false</send-email>\n";
}
xmlMessage += "</cancel-items>\n";
send(xmlMessage);
log.trace("Leaving cancelItems");
}
/** Returns the items.
*
* @param itemIds the item ids that need to be returned.
*
* @param sendEmail States if GBuy must send a mail to the buyer (true) or
* not (false).
*/
public void returnItems(final ReturnItemsRequest.ItemIds itemIds,
final boolean sendEmail) {
log.trace("Entering returnItems");
if (itemIds == null) {
throw new IllegalArgumentException("the itemInformation cannot be null");
}
if (itemIds.getItemId().isEmpty()) {
throw new IllegalArgumentException("the itemIds list cannot be empty");
}
String xmlMessage;
xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<return-items xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\">\n"
+ " <item-ids>\n";
for (ItemId item : itemIds.getItemId()) {
xmlMessage += " <item-id>\n";
if (item.getMerchantItemId() != null) {
xmlMessage += " <merchant-item-id>"
+ item.getMerchantItemId()
+ "</merchant-item-id>\n";
}
if (item.getGoogleItemId() != null) {
xmlMessage += "<google-item-id>" + item.getGoogleItemId()
+ "</google-item-id>\n";
}
xmlMessage += "</item-id>\n";
}
xmlMessage += "</item-ids>\n";
if (!sendEmail) {
xmlMessage += " <send-email>false</send-email>\n";
}
xmlMessage += "</return-items>\n";
send(xmlMessage);
log.trace("Leaving returnItems");
}
/** Backorder the indicated items.
*
* @param itemIds The item ids to be backordered.
*
* @param sendEmail States if GBuy must send a mail to the buyer (true) or
* not (false).
*/
public void backorderItems(final BackorderItemsRequest.ItemIds itemIds,
final boolean sendEmail) {
log.trace("Entering backorderItems");
if (itemIds == null) {
throw new IllegalArgumentException("the itemInformation cannot be null");
}
if (itemIds.getItemId().isEmpty()) {
throw new IllegalArgumentException(
"the itemIds list cannot be empty");
}
String xmlMessage;
xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<backorder-items xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\">\n"
+ " <item-ids>\n";
for (ItemId item : itemIds.getItemId()) {
xmlMessage += " <item-id>\n";
if (item.getMerchantItemId() != null) {
xmlMessage += " <merchant-item-id>"
+ item.getMerchantItemId()
+ "</merchant-item-id>\n";
}
if (item.getGoogleItemId() != null) {
xmlMessage += "<google-item-id>" + item.getGoogleItemId()
+ "</google-item-id>\n";
}
xmlMessage += "</item-id>\n";
}
xmlMessage += "</item-ids>\n";
if (!sendEmail) {
xmlMessage += " <send-email>false</send-email>\n";
}
xmlMessage += "</backorder-items>\n";
send(xmlMessage);
log.trace("Leaving backorderItems");
}
/** Reset the indicated items.
*
* @param itemIds The item ids needed to be reset.
*
* @param sendEmail States if GBuy must send a mail to the buyer (true) or
* not (false).
*/
public void resetItems(
final ResetItemsShippingInformationRequest.ItemIds itemIds,
final boolean sendEmail) {
log.trace("Entering resetItems");
if (itemIds == null) {
throw new IllegalArgumentException("the itemInformation cannot be null");
}
if (itemIds.getItemId().isEmpty()) {
throw new IllegalArgumentException(
"the itemIds list cannot be empty");
}
String xmlMessage;
xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<reset-items-shipping-information xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\">\n"
+ " <item-ids>\n";
for (ItemId item : itemIds.getItemId()) {
xmlMessage += " <item-id>\n";
if (item.getMerchantItemId() != null) {
xmlMessage += " <merchant-item-id>"
+ item.getMerchantItemId()
+ "</merchant-item-id>\n";
}
if (item.getGoogleItemId() != null) {
xmlMessage += "<google-item-id>" + item.getGoogleItemId()
+ "</google-item-id>\n";
}
xmlMessage += "</item-id>\n";
}
xmlMessage += "</item-ids>\n";
if (!sendEmail) {
xmlMessage += " <send-email>false</send-email>\n";
}
xmlMessage += "</reset-items-shipping-information>\n";
send(xmlMessage);
log.trace("Leaving resetItems");
}
/** Archives this order, removing it from your Merchant Account inbox.
*/
public void archive() {
log.trace("Entering archive");
String xmlMessage;
xmlMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<archive-order xmlns=\"" + NAMESPACE
+ "\" google-order-number=\"" + orderNumber + "\"/>\n";
send(xmlMessage);
log.trace("Leaving archive");
}
/** Compares two order objects.
*
* It only takes into account the order number.
*
* @param other The object to compare to.
*
* @return Returs true if both orders contain the same order number.
*/
public boolean equals(final Object other) {
if (this == other) {
return true;
}
if ((other == null) || (other.getClass() != getClass())) {
return false;
}
Order second = (Order) other;
return orderNumber.equals(second.orderNumber);
}
/** Return the hash code.
*
* @return Returns the hash code.
*/
public int hashCode() {
return orderNumber.hashCode();
}
/** Sends a message to the GBuy server corresponding to one of the order
* commands.
*
* Throws an exception if the response is not the expected one.
*
* This implementation simply searches for the string request-received and if
* not found it throws an exception.
*
* @param message The message to send to GBuy.
*/
public void send(final String message) {
String response = null;
response = transport.send(message);
// This is a very naive check to verify if the command was sucessful. It is
// compatible with XML and name/value.
if (-1 == response.indexOf("request-received")) {
throw new RuntimeException("Unexpected response from GBuy: "
+ response);
}
}
/** Sends a message to the GBuy server corresponding to one of the order
* commands.
*
* Throws an exception if the response is not the expected one.
*
* This implementation simply searches for the string request-received and if
* not found it throws an exception.
*
* @param message The message to send to GBuy.
* @return the response from Checkout.
*/
public Object sendAndReceive(final String message) {
String response = null;
response = transport.send(message);
JAXBElement node = CartUtils.unmarshal(response);
return node.getValue();
}
}