1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
|
[
// [Google_Checkout]
// Copyright 2007 by LassoSoft, LLC.
// Originally published in The June 30, 2006 Tip of the Week
// Updated with new Google API addresses January 4, 2007
// Introduction to Google Checkout
// <http://www.omnipilot.com/TotW.1768.9158.lasso>
//
// Posted on TagSwap <http://www.tagswap.net/google_checkout>
//
// Based on the Google Checkout API which can be found here
// <http://code.google.com/apis/checkout/developer/index.html>
//
// This type represents a shopping cart for the Google Checkout service
// The type is used to create a shopping cart, add one or more items to it,
// and then present either a checkout form to the site visitor or redirect
// the visitor into the Google Checkout process.
//
// This type implements much of the non-interactive Google Checkout API including
// both the direct form POST method and the alternative POST and redirect method,
// flat-rate shipping for each item, a single sales tax table for US addresses,
// many options including optional URLs, request for phone number, whether shipping
// should be taxed, button size, and more.
//
// The tip of the week linked above should be consulted for details about how to get
// started with this type. The sandbox should always be used for full testing of this
// type before it is used on a live server. The type requires a Google Merchant Key and
// Merchant ID.
//
// Example
//
// 1 - Create a cart by entering the merchant ID and key provided by Google. The edit URL
// is shown on the cart. The continue URL is shown after the visitor complets their purchase.
// The shipping name is shown as the shipping method. The taxes array sets a different tax rate
// for a local zip code, Washington state, and the rest of the US. The "sandbox" setting establishes
// this as a test connection. And, the expiration date means this cart must be checked out within 24
// hours.
//
// [Var: 'mycart' = (google_checkout:
// -merchantid='1234567890',
// -merchantkey='1234567890',
// -editurl='http://www.example.com/edit',
// -continueurl='http://www.example.com/continue',
// -shippingname='Pony Express',
// -taxes=(array: '98366'=0.085, 'WA'=0.070, 'all'=0.0),
// -sandbox=true,
// -expires=date->(add: -day=1) &)]
//
// 2 - Add items to the cart. Each item has a name, short description, price, quantity, and shipping cost.
// Note that the price and shipping cost are multipled by the quantity to get the totals. Sales tax is added
// later. The amounts should be in the currency specified in the [Google_Checkout] tag (US dollars by default).
//
// [$mycart->(additem: -name='My Widget',
// -description='My patented widget. A true original.',
// -price=159.99,
// -quantity=2,
// -shipping=10.00)]
//
// 3 - Display a form for the visitor to check out. The form will take the visitor to a Google page where they
// can log into their Google account (or create one). Once the checkout process is complete both the visitor and
// you will be sent an email confirmation. You can log in to your Google Checkout merchant account to fulfil the
// order.
//
// [$mycart->(checkoutform: -Size='large')]
//
// 4 - Alternately, if you have your own checkout button you can use the following tag to post the cart data to
// Google checkout and then redirect the user to the Google page where they can log into their Google account (as
// above). The following code shows a checkout image linked back to the current page. The conditional posts the
// form when the visitor clicks on the link.
//
// <a href="[response_filepath]?action=post">[$test->(checkoutimage: -size='large')]</a>
// [(action_param: 'action') >> 'post' ? $test->postcart]
//
Define_Type: 'Google_Checkout';
Local: 'url' = '';
Local: 'src' = '';
Local: 'mkey' = '';
Local: 'mid' = '';
Local: 'currency' = '';
Local: 'expires' = '';
Local: 'continueurl' = '';
Local: 'editurl' = '';
Local: 'taxes' = '';
Local: 'requestphone' = False;
Local: 'taxshipping' = False;
Local: 'shippingname' = '';
Local: 'items' = Array;
Local: 'cart' = '';
Local: 'dirty' = False;
// [Google_Checkout]
// This tag is called when a new Google Checkout item is created.
// The following options are possible
// -MerchantKey='KEY' (Required, Provided by Google)
// -MerchantID='ID' (Required, Provided by Google)
// -Sandbox (If specified then the cart is in Sandbox mode for testing)
// -EditURL='URL' (Allows the user to return to your site while they are viewing their cart)
// -ContinueURL='URL' (The URL for the user to return to your site after they checkout)
// -Taxes=(Array) (An array of pairs specifying the tax rules for your cart. The taxes will
// be calculated when the user checks out. Each pair should be of the form '98366'=0.080 where
// the first part is the region and the second the tax rate (e.g. 8%). Regions can be five digit
// US zip codes 98366, zip codes with a wild card 98*, two letter state abbreviations WA, or the
// words 'all' for all valid US addresses, 'full' for all addresses in the 50 states, or 'continental'
// for all addresses in the 48 contiguous states. More specific rules should come before less specific.
// -RequestPhone (If specified the phone number is requested from the purchaser)
// -TaxShipping (If specified then sales tax is charged on shipping)
// -ShippingName (A short two to three word description of the shipping method (e.g. Postal Mail or UPS Ground)
// -Currency='USD' (Defaults to US dollars)
// -Expires=Date (An expiration date for the cart)
Define_Tag: 'onCreate',
-Required='merchantkey',
-Required='merchantid',
-Optional='sandbox',
-Optional='editurl',
-Optional='continueurl',
-Optional='taxes',
-Optional='requestphone',
-Optional='taxshipping',
-Optional='shippingname',
-Optional='currency',
-Optional='expires';
Self->'mkey' = (String: #merchantkey);
Self->'mid' = (String: #merchantid);
If: (Local_Defined: 'sandbox') && ((#sandbox === null) || (#sandbox != false));
Self->'url' = 'https://sandbox.google.com/checkout/cws/v2/Merchant/' + (encode_stricturl: #merchantid) + '/';
Self->'src' = 'http://sandbox.google.com/checkout/buttons/checkout.gif?merchant_id=' + (encode_stricturl: #merchantid);
Else;
Self->'url' = 'https://checkout.google.com/cws/v2/Merchant/' + (encode_stricturl: #merchantid) + '/';
Self->'src' = 'http://checkout.google.com/buttons/checkout.gif?merchant_id=' + (encode_stricturl: #merchantid);
/If;
If: (Local_Defined: 'currency');
Self->'currency' = #currency;
Else;
Self->'currency' = 'USD';
/If;
If: (Local_Defined: 'expires') && #expires->(IsA: 'date');
Local: 'date' = #expires;
#date->gmt == false ? Local: 'date' = (Date_LocalToGMT: #date);
Self->'expires' = #date;
Else: (Local_Defined: 'expires') && (Valid_Date: #expires);
Local: 'date' = (Date: #expires);
#date->gmt == false ? Local: 'date' = (Date_LocalToGMT: #date);
Self->'expires' = #date;
/If;
If: (Local_Defined: 'editurl');
Self->'editurl' = #editurl;
/If;
If: (Local_Defined: 'continueurl');
Self->'continueurl' = #continueurl;
/If;
If: (Local_Defined: 'shippingname');
Self->'shippingname' = #shippingname;
Else;
Self->'shippingname' = 'Standard Shipping';
/If;
If: (Local_Defined: 'taxes') && (#taxes->(IsA: 'map') || #taxes->(IsA: 'array'));
Self->'taxes' = #taxes;
/If;
If: (Local_Defined: 'requestphone');
Self->'requestphone' = (#requestphone === Null || #requestphone != False);
/If;
If: (Local_Defined: 'taxshipping');
Self->'taxshipping' = (#taxshipping === Null || #taxshipping != False);
/If;
Self->'dirty' = True;
/Define_Tag;
// [String: Google_Checkout]
// This tag is called when the cart is converted to string. It does the work
// of generating the XML for the cart.
Define_Tag: 'onConvert', -Required='type';
If: (self->'dirty' == False);
Return: self->'cart';
/If;
Define_Tag: 'clean', -Required='input';
Local: 'output' = (string: #input);
#output->(replace: '&', '&');
#output->(replace: '<', '<');
#output->(replace: '>', '>');
Return: @#output;
/Define_Tag;
Local: 'output' = '';
#output += '<?xml version="1.0" encoding="UTF-8"?>\r\n';
#output += '<checkout-shopping-cart xmlns="http://checkout.google.com/schema/2">\r\n';
#output += '\t<shopping-cart>\r\n';
If: Self->'expires'->(IsA: 'date');
#output += '\t\t<cart-expiration>\r\n';
#output += '\t\t<good-until-date>' + self->'expires'->(Format: '%QT%T') + '</good-until-date>\r\n';
#output += '\t\t</cart-expiration>\r\n';
/If;
#output += '\t\t<items>\r\n';
Local: 'shipping' = 0.0;
Iterate: Self->'items', (Local: 'item');
#item->(Find: 'quantity') == 0 ? Loop_Continue;
#output += '\t\t\t<item>\r\n';
Iterate: (Array: 'item', 'item-name', 'item-description', 'unit-price', 'quantity'), (Local: 'key');
#item !>> #key ? Loop_Continue;
if: #key == 'unit-price';
#output += '\t\t\t\t<unit-price currency="' + self->'currency' + '">';
else;
#output += '\t\t\t\t<' + #key +'>';
/if;
#output += (clean: #item->(Find: #key));
#output += '</' + #key + '>\r\n';
/Iterate;
#output += '\t\t\t</item>\r\n';
If: #item >> 'shipping';
#shipping += (Integer: #item->(Find: 'quantity')) * (Decimal: #item->(Find: 'shipping'));
/If;
/Iterate;
#output += '\t\t</items>\r\n';
#output += '\t</shopping-cart>\r\n';
#output += '\t<checkout-flow-support>\r\n';
#output += '\t\t<merchant-checkout-flow-support>\r\n';
If: self->'continueurl' != '';
#output += '\t\t\t<continue-shopping-url>' + (clean: self->'continueurl') + '</continue-shopping-url>\r\n';
/If;
If: self->'editurl' != '';
#output += '\t\t\t<edit-cart-url>' + (clean: self->'editurl') + '</edit-cart-url>\r\n';
/If;
If: self->'requestphone' == True;
#output += '\t\t\t<request-buyer-phone-number>true</request-buyer-phone-number>\r\n';
/If;
If: #shipping > 0;
#output += '\t\t\t<shipping-methods>\r\n';
#output += '\t\t\t\t<flat-rate-shipping name="' + (clean: self->'shippingname') + '">\r\n';
#output += '\t\t\t\t\t<price currency="' + self->'currency' + '">' + #shipping + '</price>\r\n';
#output += '\t\t\t\t</flat-rate-shipping>\r\n';
#output += '\t\t\t</shipping-methods>\r\n';
/If;
If: self->'taxes'->(IsA: 'map') || self->'taxes'->(IsA: 'array');
#output += '\t\t\t<tax-tables>\r\n';
#output += '\t\t\t\t<default-tax-table>\r\n';
#output += '\t\t\t\t\t<tax-rules>\r\n';
Iterate: self->'taxes', (Local: 'rule');
#output += '\t\t\t\t\t\t<default-tax-rule>\r\n';
If: self->'taxshipping' == True;
#output += '\t\t\t\t\t\t\t<shipping-taxed>true</shipping-taxed>\r\n';
/If;
#output += '\t\t\t\t\t\t\t<rate>' + (Decimal: #rule->Second) + '</rate>\r\n';
#output += '\t\t\t\t\t\t\t<tax-area>\r\n';
if: #rule->First >> 'continental';
#output += '\t\t\t\t\t\t\t\t<us-country-area country-area="CONTINENTAL_48" />\r\n';
else: #rule->First >> 'all';
#output += '\t\t\t\t\t\t\t\t<us-country-area country-area="ALL" />\r\n';
else: #rule->First >> 'full';
#output += '\t\t\t\t\t\t\t\t<us-country-area country-area="FULL_50_STATES" />\r\n';
else: (String_ReplaceRegexp: #rule->First, -Find='[A-Z][A-Z]', -Replace='') == '';
#output += '\t\t\t\t\t\t\t\t<us-state-area><state>' + (clean: #rule->First) + '</state></us-state-area>\r\n';
else: (String_ReplaceRegexp: #rule->First, -Find='[0-9]+\\*?', -Replace='') == '';
#output += '\t\t\t\t\t\t\t\t<us-zip-area><zip-pattern>' + (clean: #rule->First) + '</zip-pattern></us-zip-area>\r\n';
/if;
#output += '\t\t\t\t\t\t\t</tax-area>\r\n';
#output += '\t\t\t\t\t\t</default-tax-rule>\r\n';
/Iterate;
#output += '\t\t\t\t\t</tax-rules>\r\n';
#output += '\t\t\t\t</default-tax-table>\r\n';
#output += '\t\t\t</tax-tables>\r\n';
/If;
#output += '\t\t</merchant-checkout-flow-support>\r\n';
#output += '\t</checkout-flow-support>\r\n';
#output += '</checkout-shopping-cart>\r\n';
Self->'cart' = #output;
Self->'dirty' = False;
Return: @#output;
/Define_Tag;
// [Google_Checkout->Items]
// Returns the array of items in the cart. This can be used to modify the array
// directly, change item quantities, remove items, etc.
Define_Tag: 'Items';
Self->'dirty' = True;
Return: @self->'items';
/Define_Tag;
// [Google_Checkout->AddItem]
// Adds an item to the cart. The item can have the following attributes
// -Name='Item Name' (Required)
// -Description='Short item description' (Required)
// -Price='Unit Price' (Required)
// -Quantity='Integer' (Default 1)
// -Shipping='Shipping Cost' (Default 0)
Define_Tag: 'AddItem',
-Required='name',
-Required='description',
-Required='price',
-Optional='quantity',
-Optional='shipping';
Local: 'item' = (Map:
'item-name'=#name,
'item-description'=#description,
'unit-price'=#price,
'quantity'=(Math_Max: 1, (Local: 'quantity'))
);
(Local_Defined: 'shipping') ? #item->(Insert: 'shipping'=#shipping);
Self->'dirty' = True;
Self->'items'->(Insert: @#item);
/Define_Tag;
// [Google_Checkout->GetSignature]
// Returns the HMAC SHA1 signature for the car with Base64 encoding suitable for posting to Google.
Define_Tag: 'getsignature';
If: (self->'dirty' == True);
Self->(onConvert: 'string');
/If;
Return: (Encrypt_HMAC: -password=self->'mkey', -token=(bytes: self->'cart'), -digest='sha1', -base64);
/Define_Tag;
// [Google_Checkout->GetCart]
// Returns the current cart with Base64 encoding suitable for posting to Google.
// [String: Google_Checkout] can be used to get the raw XML of the carty.
Define_Tag: 'getcart';
If: (self->'dirty' == True);
Self->(onConvert: 'string');
/If;
Return: (Encode_Base64: self->'cart');
/Define_Tag;
// [Google_Checkout->PostCart]
// Posts the cart to Google using [Include_URL] then redirects the user to view the cart.
// This alternate method of posting the cart can be used instead of the Checkout Form below.
Define_Tag: 'postcart';
If: (self->'dirty' == True);
Self->(onConvert: 'string');
/If;
Local: 'result' = (Include_URL: Self->'url' + 'request', -Username=Self->'mid', -Password=Self->'mkey', -PostParams=Self->'cart');
Local: 'url' = (String_ReplaceRegExp: #result, -Find='[\\s\\S]*<redirect-url>(.*?)</redirect-url>[\\s\\S]*', -replace='\\1');
#url->(replace: '&', '&');
Redirect_URL: #url;
/Define_Tag;
// [Google_Checkout->CheckoutForm]
// Outputs a <form> including the cart, signature, member id and key, and the standard Google Checkout image.
// -Size='small|large' (default medium)
// -Alt='Alternate text which is included on the button'
// -Style='white' (default transparent)
// -Variant='disabled' (default enabled)
Define_Tag: 'checkoutform',
-Optional='size',
-Optional='alt',
-Optional='style',
-Optional='variant';
If: (Local: 'size') >> 'large';
Local: 'width' = 180;
Local: 'height' = 46;
Else: (Local: 'size') >> 'small';
Local: 'width' = 160;
Local: 'height' = 43;
Else;
Local: 'width' = 168;
Local: 'height' = 44;
/If;
If: !(Local_Defined: 'alt');
Local: 'alt' = 'Fast checkout through Google';
/If;
Local: 'output' = '';
#output += '<form action="' + self->'url' + 'checkout" method="post">\r\n';
#output += '\t<input type="hidden" name="cart" value="' + self->getcart + '" />\r\n';
#output += '\t<input type="hidden" name="signature" value="' + self->getsignature + '" />\r\n';
#output += '\t<input type="image" name="Google Checkout" alt="' + (encode_html: #alt) + '" src="' + self->'src';
#output += '&w=' + #width + '&h=' + #height;
#output += '&style=' + (((local: 'style') >> 'white') ? 'white' | 'trans');
#output += '&variant=' + (((local: 'variant') >> 'disabled') ? 'disabled' | 'text');
#output += '&loc=en_US" height="' + #height + '" width="' + #width + '" />\r\n';
#output += '</form>\r\n';
Return: @#output;
/Define_Tag;
// [Google_Checkout->CheckoutImage]
// Outputs an <img> tag for the standard Google Checkout image
// -Size='small|large' (default medium)
// -Alt='Alternate text which is included on the button'
// -Style='white' (default transparent)
// -Variant='disabled' (default enabled)
// -Border=0 (default 0)
// -Align='left|right (default left)
Define_Tag: 'checkoutimage',
-Optional='size',
-Optional='alt',
-Optional='style',
-Optional='variant',
-Optional='border',
-Optional='align';
If: (Local: 'size') >> 'large';
Local: 'width' = 180;
Local: 'height' = 46;
Else: (Local: 'size') >> 'small';
Local: 'width' = 160;
Local: 'height' = 43;
Else;
Local: 'width' = 168;
Local: 'height' = 44;
/If;
If: !(Local_Defined: 'alt');
Local: 'alt' = 'Fast checkout through Google';
/If;
Local: 'output' = '';
#output += '<img alt="' + (encode_html: #alt) + '" src="' + self->'src';
#output += '&w=' + #width + '&h=' + #height;
#output += '&style=' + (((local: 'style') >> 'white') ? 'white' | 'trans');
#output += '&variant=' + (((local: 'variant') >> 'disabled') ? 'disabled' | 'text');
#output += '&loc=en_US" height="' + #height + '" width="' + #width + '" border="' + (integer: (local: 'border')) + '" ' + ((local_defined: 'align') ? 'align="' + #align + '" ' | '') + '/>\r\n';
Return: @#output;
/Define_Tag;
/Define_Type;
// [Encode_HMAC]
// This tag generates a keyed hash message authentication code for a given input and password.
// This tag will be included in a future version of Lasso so its definition is conditional.
//
// Both the -Password and -Token can be either byte streams or strings.
// -Password specifies the key for the hash
// -Token specifies the text message which is to be hashed
// -Digest specifies the algorithm to use for the hash (usually MD5 or SHA1)
//
// By default the output is a byte stream
// -Base64 specifies the output should be a Base64 encoded string.
// -Hex specifies the output should be a hex format string 0x0123456789abcdef
// -Cram specifies the output should be in a cram hex format 0123456789ABCDEF
//
// Note that [Encrypt_CRAMMD5] is equivalent to using this tag with -Digest='MD5' and -Cram.
//
if: !(lasso_tagexists: 'encrypt_hmac');
define_tag('hmac',
-required = 'password',
-required = 'token',
-optional = 'digest',
-optional = 'base64',
-optional = 'hex',
-optional = 'cram',
-namespace = 'encrypt_');
local(
'pbytes' = bytes,
'ibytes' = bytes,
'obytes' = bytes,
'output' = bytes);
if(local_defined: 'digest');
fail_if(cipher_list(-digest) !>> #digest, -9956, '[Encrypt_HMAC] The digest "' (string: #digest) + '" is not supported by Lasso on this machine. Supported digest types include (' + (cipher_list: -digest)->(join: ', ') + ').');
else;
local: 'digest' = 'md5';
/if;
if(#password->size > 64);
#pbytes = cipher_digest(#password, -digest=#digest);
else: #password->isa('bytes');
#pbytes = #password;
else;
#pbytes->importstring(#password, 'iso-8859-1');
/if;
iterate: #pbytes, local('byte');
#ibytes->import8bits(integer(#byte)->bitxor(0x36) &);
#obytes->import8bits(integer(#byte)->bitxor(0x5c) &);
/iterate;
if(#pbytes->size < 64);
loop(64 - #pbytes->size);
#ibytes->import8bits(0x36);
#obytes->import8bits(0x5c);
/loop;
/if;
if: #token->(isa: 'bytes');
#ibytes += #token;
else;
#ibytes->importstring(#token, 'iso-8859-1');
/if;
#obytes += cipher_digest(#ibytes, -digest=#digest);
#output = cipher_digest(#obytes, -digest=#digest);
if(local_defined('hex') && ((#hex === null) || (#hex != false)));
return('0x' + string_lowercase(encode_hex(#output)));
else(local_defined('cram') && ((#cram === null) || (#cram != false)));
return(encode_hex(#output));
else(local_defined('base64') && ((#base64 === null) || (#base64 != false)));
return(encode_base64(#output));
else;
return(@#output);
/if;
/define_tag;
/if;
]
|