For posterity, here is the solution:
1) Don't mess with Simple Pay Buttons - use FPS instead
2) Of the myriad overlapping documents, I found this to be the simplest and clearest: http://docs.aws.amazon.com/AmazonFPS/latest/FPSBasicGuide/SendingaCBUIRequest.html
3) Use encodeURIComponent not encodeURI - this was my biggest most frustrating mistake
This code will correctly sign an Amazon FPS request (assumes crypto for hmac and nconf for configuration)
var crypto = require('crypto');
var _ = require('underscore');
var nconf = require('nconf').argv().env().file({
file: "./config.json"
});
exports.azPayRequest=function (amount, desc,ref) {
var params={
"returnUrl": nconf.get("awsPayments:returnURL"), //callback
"callerKey" : nconf.get("awsPayments:callerKey"), //aws id
"callerReference": ref,
"pipelineName":"SingleUse",
"cobrandingStyle" :"logo",
"currencyCode" :"USD",
"transactionAmount" : amount,
"paymentReason" : desc,
"signatureMethod": "HmacSHA256",
"signatureVersion" :"2"
}
/*
StringToSign = HTTPVerb + "\n" +
ValueOfHostHeaderInLowercase + "\n" +
HTTPRequestURI + "\n" +
CanonicalizedQueryString <from the preceding step>
*/
//sort parameters
var p=_.pairs(params);
var psorted=_.sortBy(p, function(p) { return p[0];});
//method, host, path
var method='GET';
var host=nconf.get('awsPayments:host'); // e.g., authorize.payments.amazon.com;
var path=nconf.get('awsPayments:path'); //e.g. /cobranded-ui/actions/start;
//url encode parameters
var qstring='';
for(var i=0; i<psorted.length;i++) {
psorted[i][0]=encodeURIComponent(psorted[i][0]);
psorted[i][1]=encodeURIComponent(psorted[i][1]);
qstring+=psorted[i][0]+'='+psorted[i][1];
if (i<psorted.length-1) {qstring+='&';}
};
//calculate hmac
var nl=String.fromCharCode(10);
var encode_request=method+nl+host+nl+path+nl+qstring;
console.log("STRING TO ENCODE\n"+encode_request+'\n\n');
var sig=crypto.createHmac("SHA256", nconf.get("awsPayments:awsSecretAccessKey"))
.update(encode_request)
.digest('base64');
var url="https://"+host+path+"?"+qstring+'&signature='+encodeURIComponent(sig);
return url;
}