Wednesday, November 30, 2011

Fighting Expedia, The Conclusion

Well it finally arrived.

After four months, after being told
  • You have to follow a link (which didn't exist) on a web page
  • Your trip actually didn't qualify for the offer
  • Please hold, your call is very importa... click
  • Our offices are closed (in the middle of a weekday afternoon)
  • It's not our fault; it's the card handler's fault
the card finally arrived.

I think I'm going to go buy a bottle of Advil.

Thursday, November 10, 2011

I Appreciate What GSI Did For Me

This is something that happened a little while ago, and I've been meaning to tell you about it. Here goes.

You know I really enjoy coffee. All kinds. I've had French Press coffee before, but didn't have a press to do it myself.

We were at Mast General Store and in their camping section I found Lexan presses by GSI Outdoors. I liked the idea of a shatter-resistant carafe, instead of glass (I've gone through numerous glass espresso carafes). So I bought the press. Works great, good coffee.

Not long after I bought it, I broke the lid. Sigh. I wrote the company, asking whether I could buy a new lid. They wrote back, and said all I had to do was call them, and they'd send me a new lid. So I did, and they did too.

Thanks GSI! I appreciate it.

Wednesday, November 9, 2011

Fighting Expedia, Chapter 2

Well I wish I had something better to report. Right after I posted the last installment, I got a Twitter message from @Expedia, saying "sorry to hear you're having problems, please give us a call." I spoke again with Corey, and he again apologized, asked me to be patient, blamed various system failures, etc. I told him I'd wait to see what shakes out from BBB.

On October 8, two weeks after I filed the complaint from BBB, I got a response from Expedia through BBB, the essence of which was, sorry for the trouble, please be patient, it's not our fault, etc. I was particularly irritated by Expedia's statement: "If the card has not been received within the next two weeks then we may visit other options for this customer but we must have proof that the customer has not received the promotional item at that time."

BBB asked whether I was satisfied with their response and wanted to close the case (Seriously?) I said "No," because Expedia hadn't actually done anything. I think the case should be closed when they do what they had promised.

On October 18, I got an email from Expedia saying that the card was on its way!

Perhaps it's a good idea that I didn't hold my breath, because as of yesterday, November 8, the card hasn't arrived.

On November 1 I checked the status of the case and saw I had gotten a response (tagged "FINAL RESPONSE FROM BUSINESS") to my rejecting their earlier response (from October 8). It said they were sorry that I declined their earlier response, and they said the gift card was "resubmitted" on October 17. And because they're sorry for the inconvenience, they were giving me a $50 coupon to be used for future travel booked through Expedia. All I'd have to do is create an account with them (I used a guest account for the July trip), then call one of their reps with my case number to apply the coupon to my account.

Right.

BBB asked whether I was satisfied, and I replied "No," saying that all I want is the gift card, the coupon is worthless, and considering all the grief this has been ("your trip doesn't qualify," "you didn't click the [non-existent] link," "we're closed," on hold until disconnected, etc) I just would rather not do business with them at all. There are plenty of other ways to book travel.

The moment the card arrives, I'd be happy to consider the case resolved.

Imagine my surprise when I later log in to BBB to view the case, and find its status is "Closed."

11/01/2011 JAF BBB BBB judged complaint addressed
11/01/2011 Otto BBB Inform Consumer - Case Closed AJR
11/01/2011 Otto BBB Inform Business - Case Closed AJR
11/01/2011 Otto BBB Case Closed AJR

So I sent BBB a note asking "what gives?" I would have expected that the case would stay open until the case was actually resolved ie, that Expedia had done what they said they would do). BBB replied saying that they don't just hold cases open, but they said I could open another case if the card doesn't come. Thanks, BBB.

So after a month or so of trying to go the BBB route, BBB has not been a whole lot of help resolving the issue, and the card hasn't arrived.

Thursday, September 29, 2011

Fighting Expedia

Retailers often entice customers into a purchasing decision by offering a rebate, bonus, or other type of promotion. The retailer expects, however, that a portion of their customers will not follow through on claiming the rebate. There's even a term for it: "breakage."

Back in July my wife and I made a reservation through Expedia for a stay in St Petersburg, Florida. We had a great time, no problem there. When we made the reservation, however, Expedia had an offer for a $50 Gas Card on completion of the stay. We thought, "That's nice, it'll help cover the cost of the gas to get there."



When we got back home, we went to the Expedia site to find out how to claim the $50 gas card. Their emailed response to our inquiry told us to follow a link, which actually did not exist. So when I called their customer support line, I was informed that our trip did not qualify for the $50 gift card. I pointed out that it did, and the rep forwarded me up the chain. That next rep dropped my call. I was sitting at the computer while on hold, and I posted a tweet: something like "I'm having a hard time solving a problem with @expedia" (I had later deleted the post). Very soon after that I got a message inviting me to call them, and they actually gave my wife a phone call about the issue. I called Expedia back and spoke at length with one of their reps, who apologized profusely and blamed various system failures, and promised that we'd receive the gas card within the next week or two (the itinerary status screen said the estimated delivery was 8/26). About this time (August 2) I made a post saying that I was happy with how Expedia was helping to solve the problem, and deleted the earlier post about the original issue. (I've not deleted the happy post: it's shown below.)



Fast forward to today, about a month past the delivery date. There's no sign of the gas card. I called Expedia, trying to contact the same rep I had spoken with two months prior, but instead the operator sent me to their "Tier 3 Service Department". After a few minutes on hold, a message on an answering machine informed me that their offices were closed (it was 3PM Eastern), and told me to leave a message. So I did. I gave them my name, itinerary number, and phone number.

It started to seem to me that they were not going to fulfill the offer, and there was nothing I could do about it.

Except complain. And I generally don't like complaining.

First stop: Google. A search for "expedia gas card problem" will show you a lot of people have similar issues.

Next stop, the Better Business Bureau. I was very surprised to find the BBB gives them an A+ rating. Wow. How'd they manage that? Anyway I filed a complaint, and I got a message back from BBB saying they've forwarded the issue to Expedia. I should hear something by middle of next month.

One of the things the BBB complaint asked for was what did I want Expedia to do. My response was basically, just honor the gas card like they said they would. But I still hate all the aggravation they have caused. What should they do to make that right?

Takeaways:
1. Try not to let the promise of a Free __________ unduly influence a purchase decision. Have you ever noticed how tough it can be to claim a rebate? To use those frequent flier miles?
2. Try to be reasonable; try to be patient.
3. Don't be afraid to involve the BBB, especially dealing with a BBB accredited business.
4. You have more influence than you think you do.

Tuesday, June 7, 2011

Let's Reduce Teen Unemployment

Unemployment among teens is 24%.

Let's reduce teen unemployment. Here are a couple of thought experiments, and then a suggestion.

1. Pretend you are holding a garage sale this Saturday. You've got a lot of stuff to get rid of, and so you decide to make things simple on your neighbors, you will price each individual item at $10. At the end of the sale, you might have $20-$30, and a ton of your unsold stuff.

2. Pretend that all the gas stations in your state decide to sell gasoline at $10 per gallon. What will happen is that everybody is going to complain, some people will drive across the border to get to cheaper gas, some people won't drive at all, and a few desperate souls will endure the new expense. The gas stations, though, will find they aren't selling much gas anymore.

Price floors create surplus.

A minimum wage law is a price floor for workers. A minimum wage law is going to create surplus workers, that is, unemployment.

What if the minimum wage law were only to apply to workers over 21 years old? What if an employer were free to pay a teenaged worker a minimum of say 75% of minimum wage? (I'm suggesting a minimum, not a maximum wage.) What would happen to teen unemployment? My guess is teen unemployment would drop like a rock.

Monday, May 30, 2011

iPhone Development: Accessing SOAP Services with WSDL2ObjC Part III

Has it been that long? My how time flies. Last time I wrote about WSDL2ObjC, we built a program which used a SOAP service to return us a barcode image. This time we're going to look at asynchronous calls, and we're going to build a small app which tells us what the current weather conditions are for a particular ZIP code.

Asynchronous calls are nice because we don't have to wait for them to complete. We issue the call, and then continue with whatever task we were performing. If we make a call from the same code that handles our user interface, our UI does not hang while we are waiting for completion of the call. I'm going to start this tutorial from scratch; I don't assume that you have gone through the first or second tutorials. But you can go through them, if you'd like.

A silver lining to the fact it has taken me so long to get to this third tutorial is that I will be using Apple's [relatively] new Xcode 4 to write the app.

Here's what we need:
  • WSDL2ObjC I'm using WSDL2ObjC-0.7-pre1.zip for this tutorial. You can get this from the project's home page.
  • A web service to consume. We're going to use the "Weather" service (http://wsf.cdyne.com/WeatherWS/Weather.asmx) which will, given a zip code, provide the local weather conditions for that zip code.
Let's go.

1. Generate the code stubs with WSDL2ObjC.
Start WSDL2ObjC. In the first field, enter the name of the source WSDL. You can specify a local file, or a web address. For this demo, enter http://wsf.cdyne.com/WeatherWS/Weather.asmx?wsdl
In the second field, specify a directory on your machine into which WSDL2ObjC will write the new code. When you click "Parse WSDL", you will see a few messages, one of which should say that it is generating Objective C code into the output directory. Check the target directory to make sure your files are there.


2. Create the project in Xcode.
Start Xcode and create a new View-based application. Call it anything you would like. In the .h file for the controller, add an action method called query, and several UITextField outlets for the fields we're going to use: zip, city, conditions, temp, humidity, and wind.

While our app is making the SOAP call, we are going to start up a UIActivityIndicatorView and tell it to spin, and when the call has completed, we're going to stop the spinner. Add another outlet called spinner, and it's of type UIActivityIndicatorView. Your .h file should look something like this:


@interface SoapWeatherViewController : UIViewController <WeatherSoapBindingResponseDelegate>
{
UITextField *zip;
UITextField *city;
UITextField *conditions;
UITextField *temp;
UITextField *humidity;
UITextField *wind;

UIActivityIndicatorView *spinner;

}

@property (nonatomic, retain) IBOutlet UITextField *zip;
@property (nonatomic, retain) IBOutlet UITextField *city;
@property (nonatomic, retain) IBOutlet UITextField *conditions;
@property (nonatomic, retain) IBOutlet UITextField *temp;
@property (nonatomic, retain) IBOutlet UITextField *humidity;
@property (nonatomic, retain) IBOutlet UITextField *wind;

@property (nonatomic, retain) IBOutlet UIActivityIndicatorView *spinner;

-(IBAction)query;

@end


Because we want our class informed when our SOAP call completes, we are going to designate it as being the delegate for the call, so we want it to implement the WeatherSoapBindingResponseDelegate protocol.

Now in the .m file, add lines to @synthesize our IBOutlets, and make sure you dealloc the UITextFields and UIActivityIndicatorView in dealloc.

Let's write the query method. This is the event that will occur when we click the "What's it like" button in the app. Let's just write boilerplate for the moment:

-(void)query { }

3. Build the UI
Double-click the XIB file for your controller to load it into Interface Builder. You will want to add a button to the NIB; title it "What's it like?" and wire its Touch Up Inside event to the query action. Add several UITextFields and labels, and connect all the outlets. And finally, add an Activity Indicator View.

It should look something like this when you're finished:


4. Pull in the WSDL2ObjC generated code.
Let's pull in the code WSDL2ObjC wrote for us. In "Other Sources," add a new group named "Weather". With Finder, drag all the files that WSDL2ObjC created into the new group. Tell Xcode to copy the items into the destination folder.

Take a look at Weather.h. There's a ton of stuff in there: definitions for bindings, responses, requests, etc. This code represents the data that WSDL2ObjC pulled out of Weather's WSDL.
Before compiling, we have to do two things to our build target: Double click on the project name to bring up the build settings.
  • In the Other Linker Flags section, add -lxml2 to the Linker Flags.
  • In the Other C Flags section, add -I/usr/include/libxml2

5. Write
Time to write some code. Open the .m for your controller, and #import "Weather.h". Now let's write our query method:

-(void)query
{
[zip resignFirstResponder];

WeatherSoapBinding *binding = [[Weather WeatherSoapBinding] initWithAddress:@"http://wsf.cdyne.com/WeatherWS/Weather.asmx"];
binding.logXMLInOut = YES;

Weather_GetCityWeatherByZIP *parms = [[Weather_GetCityWeatherByZIP alloc] init];
parms.ZIP = self.zip.text;

[binding GetCityWeatherByZIPAsyncUsingParameters:parms delegate:self];

[spinner startAnimating];
[parms release];
}



Zip calls resignFirstResponder so the keyboard will go away. We then create a binding and associate it with our service endpoint. We then create a request, fill in the blanks, then call the service.

The point of an asynchronous call is that we don't have to wait for it to finish. We don't have to halt our user interface, we can carry on with other tasks, etc. But we do have to tell the binding what to do when the call is finished. We give it an object which implements the WeatherSoapBindingResponseDelegate protocol (which conveniently is the controller itself [see the .h file]).

Declaring the controller as something which implements WeatherSoapBindignResponseDelegate means we have to write code for that method. Here it is
#pragma mark -
#pragma mark WeatherSoapBindingResponseDelegate methods
- (void) operation:(WeatherSoapBindingOperation *)operation completedWithResponse:(WeatherSoapBindingResponse *)response
{
[NSThread sleepForTimeInterval:5.0];

// step 1 fill in the blanks.
for (id mine in response.bodyParts)
{
 if ([mine isKindOfClass:[Weather_GetCityWeatherByZIPResponse class]])
 {
     self.city.text = [[mine GetCityWeatherByZIPResult] WeatherStationCity];
     self.conditions.text = [[mine GetCityWeatherByZIPResult] Description];
     self.temp.text = [[mine GetCityWeatherByZIPResult] Temperature];
     self.humidity.text = [[mine GetCityWeatherByZIPResult] RelativeHumidity];
     self.wind.text = [[mine GetCityWeatherByZIPResult] Wind];     
 }
}
// step 2 shut off the activity indicator, because we're done!
[spinner stopAnimating];
}

When the call completes this method is called. We sleep for five seconds (to enjoy watching the spinner), then fill in the blanks with the results of our query. Finally, we stop the activity indicator.

6. Run.
Save everything. Build and run the application. Enter your zip code into the field, then click "What's it like." You should see the spinner start spinning:
After about five seconds, you should see the weather for that location:

It's about 90, with a gentle breeze at the moment. Great weather for a motorcycle ride!

Hope this has been helpful; let me know what you think.

Monday, April 18, 2011

I appreciate what KBC did for me

Maria models my VR-1
I really like my KBC VR-1 helmet. Very light and comfortable, it was a great purchase from my local bike shop. I never ride without it. A few months ago my left base plate broke, so the visor would tend to fall off if I tried to open it. The bike shop didn't carry replacement plates for that helmet, and although I looked online for a replacement plate, I was unable to find one for that particular helmet. Replacement plates for other helmets ran about ten bucks, so I knew what I could expect to pay when I found one.

So I wrote the company, asking them where I could get a plate. They said I could get one from KBC themselves, and they sent me a set for free. I received the new plates (they sent me one for each side) and now my helmet is as good as new.

Thanks, KBC! I appreciate it very much.