1 # ===================================================================== 2 # cartCheckout: W-TW shopping cart checkout processor. 3 # 4 # Copyright (c) 2009,2010 Carlo Strozzi 5 # 6 # This program is free software; you can redistribute it and/or modify 7 # it under the terms of the GNU General Public License as published by 8 # the Free Software Foundation; version 2 dated June, 1991. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU General Public License for more details. 14 # 15 # You should have received a copy of the GNU General Public License 16 # along with this program; if not, write to the Free Software 17 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 # 19 # ===================================================================== 20 21 # ===================================================================== 22 # Local variables and functions 23 # ===================================================================== 24 25 cgi.group = () 26 cgi.group.literal = () 27 28 cgi.ord.fullname = () 29 cgi.ord.nick = () 30 cgi.ord.email = () 31 cgi.ord.phone = - # mandatory 32 cgi.ord.town = () 33 cgi.ord.addr = () 34 cgi.ord.zip = () 35 cgi.ord.state = () 36 cgi.ord.ctry = () 37 cgi.ord.iorg = () 38 cgi.ord.itown = () 39 cgi.ord.iaddr = () 40 cgi.ord.izip = () 41 cgi.ord.istate = () 42 cgi.ord.ictry = () 43 cgi.ord.vat = () 44 cgi.ord.ship = 0 # default 45 cgi.ord.ship.descr = () 46 cgi.ord.pay.credit = () 47 48 cgi.privacy = () 49 cgi.terms = () 50 cgi.captcha = () 51 52 tmp_ord = /dev/null 53 ord_values = /dev/null 54 55 pay_text = /dev/null 56 tw_pay_defer = () 57 58 # ===================================================================== 59 # Main program 60 # ===================================================================== 61 62 # Load call arguments. 63 csaGetArgs POST 64 65 . $CSA_ROOT/lib/group-stuff.rc 66 67 . $CSA_ROOT/lib/group-editor.rc 68 69 . $CSA_ROOT/lib/tpl-stuff.rc 70 71 # Prepare the error message "back" button. 72 # 73 #if (csaIsFullPath --exists --quiet $CSA_TPL_ROOT/tw-nav-back.txt) { 74 # # custom "back" action template. 75 # tpl.include.nav.next = $CSA_TPL_ROOT/tw-nav-back.txt 76 #} else { 77 # # default "back" action template. 78 # tpl.include.nav.next = $tw_dstem/tw-nav-back.txt 79 #} 80 81 # Is shopping allowed to non-authenticated buyers ? 82 83 if (!csaTrue $CSA_AUTH_OK) { 84 85 if (!csaTrue $TNS_BUY_ANONYMOUS) { 86 87 # Show self-registration form if appropriate. 88 csaIsInteractive && ~ $TNS_SELFREG_AUTH *[a-z]* && 89 csaTrue $TNS_SELFREG_AUTOASK && csaExit.location $CSA_RPC_URI'?0=showStatic&x-csa-lang='$CSA_LANG^'&1='$'cgi.group'^'&2=tw-reg-form' 90 91 csaExit.needauth 92 } 93 } 94 95 # This program can only be run if a non-empty shopping cart file exists 96 # in session. I proceed from the least computationally-expensive test 97 # to the most expensive one. 98 99 ~ $CSA_SESSION(19) tag:* || csaExit.fault --back 1040 100 101 # To prevent races from the cleanup scripts I need to touch 102 # the cart into existence first. If the cart already exixts then 103 # this will update its mtime value, thus preventing it from 104 # being removed soon after our tests. 105 106 touch $TMPDIR/$CSA_SESSION(19)^-crt || csaExit.fault 0003 touch 107 108 # Now the cart exists, but we also want it to be non-empty. 109 ~ `{wc -l < $TMPDIR/$CSA_SESSION(19)^-crt} 0 1 && csaExit.fault --back 1040 110 111 # Shipping info. 112 ~ $'cgi.ord.fullname' () && csaExit.fault --back 1041 $'nls.shop.fullname' 113 ~ $'cgi.ord.addr' () && csaExit.fault --back 1041 $'nls.shop.addr' 114 ~ $'cgi.ord.town' () && csaExit.fault --back 1041 $'nls.shop.town' 115 ~ $'cgi.ord.state' () && csaExit.fault --back 1041 $'nls.shop.state' 116 ~ $'cgi.ord.zip' () && csaExit.fault --back 1041 $'nls.shop.zip' 117 ~ $'cgi.ord.ctry' () && csaExit.fault --back 1041 $'nls.shop.ctry' 118 119 if (!~ $'cgi.ord.pay.credit' ()) { 120 121 # Make sure file exists. 122 touch $tw_gstem/pc+dat || csaExit.fault 0009 $tw_gstem/pc+dat 123 124 # Credits may be subject to updates, so whenever pc+dat is 125 # opened for update the relevant PLS on order+dat must have 126 # already been set. 127 csaOpen --fast --relaxed $tw_gstem/pc+dat || csaExit.fault 128 129 # Load credit data. 130 * = `{grep -qi '^'$'cgi.ord.pay.credit'$tab $tw_gstem/pc+dat} 131 132 # A purchase credit entered is invalid if it is not listed in pc+dat. 133 # Matching is case-insensitive, just in case. 134 135 ~ $3 % + [1-9]* || csaExit.fault --back 1042 136 137 # This value is currently unused, but in the future it will 138 # be used by both shopCheckoutXHTML.awk and shopCheckoutTXT.awk 139 # to compute the discount amount. 140 tw_credit = $* 141 } 142 143 # Missing billing info default to the corresponding shipping values. 144 ~ $'cgi.ord.iorg' () && cgi.ord.iorg = $'cgi.ord.fullname' 145 ~ $'cgi.ord.iaddr' () && cgi.ord.iaddr = $'cgi.ord.addr' 146 ~ $'cgi.ord.itown' () && cgi.ord.itown = $'cgi.ord.town' 147 ~ $'cgi.ord.istate' () && cgi.ord.istate = $'cgi.ord.state' 148 ~ $'cgi.ord.izip' () && cgi.ord.izip = $'cgi.ord.zip' 149 ~ $'cgi.ord.ictry' () && cgi.ord.ictry = $'cgi.ord.ctry' 150 151 # Contact info. Phone no. is optional, but if present it must be 152 # in an acceptable form. 153 ~ $'cgi.ord.email' () && csaExit.fault --back 1041 $'nls.shop.email' 154 ~ $'cgi.ord.phone' - && csaExit.fault --back 1041 $'nls.shop.phone' 155 156 # Shipping method. Currently supported methods are numerics 0-9. 157 # "0" should be used to indicate that the ordered goods will be 158 # collected by the buyer, with no need for shipment. 159 160 ~ $'cgi.ord.ship' [0-9] || csaExit.fault 0038 161 ~ $'cgi.ord.ship.descr' () && cgi.ord.ship.descr = $'nls.shop.default' 162 163 ~ $'cgi.privacy' y || csaExit.fault --back 1043 164 165 ~ $'cgi.terms' y || csaExit.fault --back 1044 166 167 if (~ $'cgi.captcha' () || !~ $'cgi.captcha' $CSA_SESSION(10)) { 168 csaExit.fault --back 401 1018 169 } 170 171 # Restore old value if none of the above errors occurred. 172 #csaChop $CSA_EXIT_SCRIPT; CSA_EXIT_SCRIPT = $CSA_RESULT 173 174 # Create a summary of the purchase order, in XHTML. 175 176 csaMkTemp tmp_ord 177 178 # Set the resulting table apart for later, to save one potentially 179 # costly jointable(1). 180 181 sorttable --input $TMPDIR/$CSA_SESSION(19)^-crt | 182 jointable $tw_gstem/page+dat | 183 getcolumn k_page cart_qty p_^(name uri descr link store) \ 184 p_etime > $tmp_ord 185 186 csaStatus || csaExit.fault 0003 sorttable/jointable/getcolumn 187 188 # I cannot use csaSetClear here, because cgi.* vars are still needed 189 # further down. 190 191 tpl.var.tw.ord.fullname = $'cgi.ord.fullname' 192 tpl.var.tw.ord.nick = $'cgi.ord.nick' 193 tpl.var.tw.ord.email = $'cgi.ord.email' 194 tpl.var.tw.ord.phone = $'cgi.ord.phone' 195 tpl.var.tw.ord.town = $'cgi.ord.town' 196 tpl.var.tw.ord.addr = $'cgi.ord.addr' 197 tpl.var.tw.ord.zip = $'cgi.ord.zip' 198 tpl.var.tw.ord.state = $'cgi.ord.state' 199 tpl.var.tw.ord.ctry = $'cgi.ord.ctry' 200 201 tpl.var.tw.ord.iorg = $'cgi.ord.iorg' 202 tpl.var.tw.ord.vat = $'cgi.ord.vat' 203 tpl.var.tw.ord.itown = $'cgi.ord.itown' 204 tpl.var.tw.ord.iaddr = $'cgi.ord.iaddr' 205 tpl.var.tw.ord.izip = $'cgi.ord.izip' 206 tpl.var.tw.ord.istate = $'cgi.ord.istate' 207 tpl.var.tw.ord.ictry = $'cgi.ord.ictry' 208 209 tpl.var.tw.ord.ship = $'cgi.ord.ship' 210 tpl.var.tw.ord.ship.descr = $'cgi.ord.ship.descr' 211 tpl.var.tw.ord.pay.credit = $'cgi.ord.pay.credit' 212 213 # AWK message write-back file. 214 csaMkTemp tmp_msg; touch $tmp_msg || csaExit.fault 0003 touch 215 216 csaAwkCmd shopCheckoutXHTML.awk 217 218 $CSA_RESULT < $tmp_ord > $tmp1 || csaExit.fault 0003 AWK 219 220 # See if AWK had any complaints. 221 . $tmp_msg 222 223 #csaExit.pcdata $tmp1 224 225 tpl.include.tw.page = $tmp1 226 227 # Include additional order notes if available. Note the use 228 # of the general-purpose inclusion tag "tpl.include.tw.text". 229 csaIsFullPath --exists --quiet $TMPDIR/$CSA_SESSION(19)^-txt && 230 tpl.include.tw.text = $TMPDIR/$CSA_SESSION(19)^-txt 231 232 if (!csaIsConfirmed) { 233 234 csaSetClear tpl.var.tw.captcha cgi.captcha 235 236 if (csaIsFullPath --exists --quiet \ 237 $CSA_TPL_ROOT/tw-shop-form-confirm.txt) { 238 # custom registration confirmation template. 239 tpl.include.html.body = $CSA_TPL_ROOT/tw-shop-form-confirm.txt 240 } else { 241 # default editing confirmation template. 242 tpl.include.html.body = $tw_dstem/tw-shop-form-confirm.txt 243 } 244 245 # PRG confirmation page must be regarded as a view. 246 tpl.if.tw.ispage = '(::DEL:)' 247 tpl.fi.tw.ispage = '(:DEL::)' 248 tpl.if.tw.printable = '(::DEL:)' 249 tpl.fi.tw.printable = '(:DEL::)' 250 tpl.if.tw.isview = () 251 tpl.fi.tw.isview = () 252 253 csaExit.ok $tpl_file 254 } 255 256 # create new DB node 257 makeNode $CSA_LANG/$'cgi.group' $CSA_SESSION(19) 258 259 tpl.var.tw.ord.num = $node_num 260 261 tbl_order.k_node = $node_num 262 tbl_order.k_order = $CSA_SESSION(19) 263 tbl_order.o_ctime = $CSA_TIME_ISO8601 264 tbl_order.o_creip = $REMOTE_ADDR 265 266 tbl_order.k_user = $CSA_SESSION(1) # may be null if anonymous. 267 268 csaSetClear tbl_order.o_fullname cgi.ord.fullname 269 csaSetClear tbl_order.o_nick cgi.ord.nick 270 csaSetClear tbl_order.o_email cgi.ord.email 271 csaSetClear tbl_order.o_phone cgi.ord.phone 272 csaSetClear tbl_order.o_town cgi.ord.town 273 csaSetClear tbl_order.o_addr cgi.ord.addr 274 csaSetClear tbl_order.o_zip cgi.ord.zip 275 csaSetClear tbl_order.o_state cgi.ord.state 276 csaSetClear tbl_order.o_ctry cgi.ord.ctry 277 278 csaSetClear tbl_order.o_iorg cgi.ord.iorg 279 csaSetClear tbl_order.o_vat cgi.ord.vat 280 csaSetClear tbl_order.o_iaddr cgi.ord.iaddr 281 csaSetClear tbl_order.o_itown cgi.ord.itown 282 csaSetClear tbl_order.o_izip cgi.ord.izip 283 csaSetClear tbl_order.o_istate cgi.ord.istate 284 csaSetClear tbl_order.o_ictry cgi.ord.ictry 285 286 csaSetClear tbl_order.o_ship cgi.ord.ship 287 csaSetClear tbl_order.o_credit cgi.ord.pay.credit 288 289 # Save descriptions along with flags in order table. 290 tbl_order.o_ship = $'tbl_order.o_ship':$'cgi.ord.ship.descr' 291 292 # Compute a generic "order password", to be used wherever needed. 293 # This must NEVER be sent to the user, not until she is entitled to 294 # receive it, if ever (i.e. she has already paid for downloadable 295 # stuff, etc.). This feature is for future use. 296 297 tbl_order.o_pw = `{pwgen -1 -A -B 6} 298 ~ $bqstatus 0 || csaExit.fault 0003 pwgen 299 300 # Set Principal Lock Semaphore(s) (PLS). 301 csaLock $tw_gstem/order+dat || csaExit.fault 302 303 # Create shop order table if it does not yet exist. 304 if (!csaIsFullPath --exists --quiet $tw_gstem/order+dat) { 305 maketable --input \ 306 $CSA_ROOT/lib/order.xrf > $tw_gstem/order+dat || 307 csaExit.fault 0003 maketable 308 } 309 310 csaOpen $tw_gstem/order+dat || csaExit.fault 311 312 envtotable --match '^tbl_order__2e[a-z]' \ 313 --strip-names '^tbl_order__2e' | getcolumn --no-header \ 314 `{maketable --names-only --input $CSA_ROOT/lib/order.xrf} >> $CSA_RESULT 315 316 csaStatus || csaExit.fault 0003 envtotable/getcolumn/maketable 317 318 # Here I need to built a second tabular view, beside the one created 319 # above in XHTML. This second view will be in plain tabular text, to 320 # be included in the confirmation e-mail messages. 321 322 csaMkTemp ord_values 323 touch $ord_values || csaExit.fault 0009 $ord_values 324 csaAwkCmd shopCheckoutTXT.awk 325 326 $CSA_RESULT < $tmp_ord | 327 setnames $'nls.shop.cols' | prtable -l300 '-s|' > $tmp1 328 329 csaStatus || csaExit.fault 0003 AWK/setnames/prtable 330 331 . $ord_values || csaExit.fault 0008 $ord_values 332 333 # Pick the most appropriate payment directions, to be included 334 # in the confirmation e-mail. 335 336 if (~ $tw_pay_defer -1) { 337 tpl_pay = tw-shop-howpay-defer.txt 338 } else tpl_pay = tw-shop-howpay.txt 339 340 if (!csaIsFullPath --exists --quiet $CSA_TPL_ROOT/$tpl_pay) { 341 tpl.include.tw.page2 = $tw_dstem/$tpl_pay 342 } else tpl.include.tw.page2 = $CSA_TPL_ROOT/$tpl_pay 343 344 # Now perform an explicit csaCommit and then send confirmation e-mail 345 # messages. Note that I do not test for errors from 'csaSendMail' 346 # because the operation has already been commited and if an error occurs 347 # while sending the messages then it's too late. In the future I may 348 # rather tell the shopper that her order was accepted but there was 349 # an error, so she had better to contact the merchant off-band. 350 351 csaLoadLib csaEmaillib.rc || csaExit.fault 352 353 csaCommit || csaExit.fault 354 355 # Only now the shopping-cart file can be safely removed. 356 csaTrapFile $TMPDIR/$CSA_SESSION(19)^-crt 357 358 # Send the confirmation message to the merchant first. 359 # If no custom email template exists then use default. 360 if (!csaIsFullPath --exists \ 361 --quiet $CSA_TPL_ROOT/tw-shop-email-seller.txt) { 362 tpl_file = (--file-root $tw_dstem tw-shop-email-seller.txt) 363 } else tpl_file = tw-shop-email-seller.txt 364 365 TNS_EMAIL_FROM = $'tpl.var.tw.ord.email' 366 367 csaSendMail $tpl_file 368 369 # Send the confirmation message to the shopper. 370 # If no custom email template exists then use default. 371 if (!csaIsFullPath --exists \ 372 --quiet $CSA_TPL_ROOT/tw-shop-email-buyer.txt) { 373 tpl_file = (--file-root $tw_dstem tw-shop-email-buyer.txt) 374 } else tpl_file = tw-shop-email-buyer.txt 375 376 TNS_EMAIL_TO = $'tpl.var.tw.ord.email' 377 378 csaSendMail $tpl_file 379 380 ~ $TNS_BUY_THANKYOU http://* https://* || 381 TNS_BUY_THANKYOU = $CSA_RPC_URI/$CSA_LANG/$'cgi.group'/$TNS_GROUP_HOME(2) 382 383 # Redirection would be doen by 'csaExit.ok ' only if we are in 384 # interactive mode, and *only* if there is something to commit. Since 385 # I have already committed explicitly, then I can no longer rely on 386 # automatic PRG on the part of csaExit.ok. 387 388 csaIsInteractive && csaExit.location $TNS_BUY_THANKYOU 389 390 csaExit.ok 391 392 # End of program.