#!/usr/bin/env bash
:<<'__comments__'
    Name: do-express-checkout-payment
   Usage: see show_usage()
Synopsis: Confirm a PayPal sandbox Express Checkout and accept a buyer payment.
   Notes: See paypal-nvp-express-checkout-how-to.md
  Author: G. D. LaBossiere, Xview Solutions Inc. <code@XviewSolutions.com>
 Release: 1
    Date: 2022-01-26
     RCS: $Id$
 License: GNU GPL v3 <http://www.gnu.org/licenses/gpl-3.0.txt>
__comments__
#------------------------------------------------------------------------------
#  bash environment
#------------------------------------------------------------------------------
#  BASH_VERSINFO[@]
#  FUNCNAME
#------------------------------------------------------------------------------
#  constants
#------------------------------------------------------------------------------
PAYPAL_CURRENCIES=('USD')
PAYPAL_METHOD="DoExpressCheckoutPayment"
PAYPAL_NVP_URL="https://api-3t.sandbox.paypal.com/nvp"
PAYPAL_PWD=""
PAYPAL_SIGNATURE=""
PAYPAL_USER=""
#------------------------------------------------------------------------------
#  error codes
#------------------------------------------------------------------------------
ERROR_10="Requires bash 4.2+"
ERROR_15="Missing required program(s)"
ERROR_17="Required global(s) unset"
#------------------------------------------------------------------------------
#  variables
#------------------------------------------------------------------------------
argList=()
checkoutId=""
currencyCode=""
errFuncName=""
errorNumber=""
payerId=""
paymentAmount=""
paypalReply=""
paypalToken=""
showUsage=""
#------------------------------------------------------------------------------
#  functions
#------------------------------------------------------------------------------
exit_handler() {
  errorNumber="$?"
  [[ $errorNumber -eq 0 ]] && return 0
  local errtxt="ERROR_${errorNumber}"
  local errmsg="Error ${errorNumber}: No description available"
  [[ ${!errtxt} ]] && errmsg="Error ${errorNumber}: ${!errtxt}"
  [[ ${errFuncName} ]] && errmsg="${errFuncName}: ${errmsg}"
  errmsg="${0##*/}: ${errmsg}"
  printf '%s\n' "${errmsg}"
  return $errorNumber
}
#------------------------------------------------------------------------------
generate_reply() {
  [[ $showUsage ]] && return 0
  errFuncName="$FUNCNAME"
  type -p curl &>/dev/null || exit 15
  local args
  args="-d METHOD=${PAYPAL_METHOD} -d VERSION=100"
  args="${args} -d PAYMENTREQUEST_0_PAYMENTACTION=Sale"
  args="${args} -d PWD=${PAYPAL_PWD}"
  args="${args} -d SIGNATURE=${PAYPAL_SIGNATURE}"
  args="${args} -d USER=${PAYPAL_USER}"
  args="${args} -d PAYMENTREQUEST_0_AMT=${paymentAmount}"
  args="${args} -d PAYMENTREQUEST_0_ITEMAMT=${paymentAmount}"
  args="${args} -d PAYMENTREQUEST_0_CURRENCYCODE=${currencyCode}"
  args="${args} -d PAYERID=${payerId} -d TOKEN=${paypalToken}"
  [[ $checkoutId ]] && args="${args} -d MSGSUBID=${checkoutId}"
  paypalReply=$(curl ${PAYPAL_NVP_URL} -s -k -m 10 ${args})
  return 0
}
#------------------------------------------------------------------------------
parse_command_line() {
  errFuncName="$FUNCNAME"
  local arg=0 args=("${argList[@]}")
  [[ ${#args[@]} -lt 10 ]] && showUsage="true" && return 0
  while [[ $arg -lt ${#args[@]} ]]; do
    case "${args[$arg]}" in
      -a|--amount)
        ((++arg))
        paymentAmount="${args[$arg]}"
        [[ $paymentAmount =~ ^[1-9]{1}[0-9]*(\.[0-9]{1,2})*$ ]] || \
          paymentAmount=""
        ((++arg))
      ;;
      -c|--currency)
        ((++arg))
        currencyCode="${args[$arg]^^}"
        [[ ${PAYPAL_CURRENCIES[@]^^} =~ $currencyCode ]] || currencyCode=""
        [[ ${#currencyCode} -eq 3 ]] || currencyCode=""
        ((++arg))
      ;;
      -i|--id)
        ((++arg))
        checkoutId="${args[$arg]}"
        [[ $checkoutId =~ ^([0-9a-zA-Z]{0,38})$ ]] || checkoutId=""
        ((++arg))
      ;;
      -p|--payer)
        ((++arg))
        payerId="${args[$arg]}"
        [[ $payerId =~ ^[[:alnum:]]{13}$ ]] || payerId=""
        ((++arg))
      ;;
      -t|--token)
        ((++arg))
        paypalToken="${args[$arg]}"
        [[ ${#paypalToken} -eq 20 ]] || paypalToken=""
        ((++arg))
      ;;
      *)
        ((++arg))
      ;;
    esac
  done
  [[ $currencyCode && $payerId && $paymentAmount && $paypalToken ]] || \
    showUsage="true"
  return 0
}
#------------------------------------------------------------------------------
print_reply() {
  [[ $showUsage ]] && return 0
  errFuncName="$FUNCNAME"
  [[ $paypalReply ]] || exit 17
  type -p cgi2shell &>/dev/null || exit 15
  local i receipt token
#  replace ampersands with newlines; read newline-delimited string into array
  readarray -t paypalReply <<< "${paypalReply//\&/$'\n'}"
#  convert the url-encoded array elements to name="value" shell format; save
#+ the result as a string containing newlines
  paypalReply=$(cgi2shell "${paypalReply[@]}")
#  read the string contents back into an array
  readarray -t paypalReply <<< "${paypalReply}"
#  parse the array for the Receipt_id and paypal token values
  for ((i=0;i<${#paypalReply[@]};i++)); do
    [[ ${paypalReply[$i]^^} =~ ^TOKEN= ]] && \
      token="${paypalReply[$i]//\"/}" && token="${token#*\=}"
    [[ ${paypalReply[$i]^^} =~ ^MSGSUBID= ]] && \
      receipt="${paypalReply[$i]//\"/}" && receipt="${receipt#*\=}"
    [[ $receipt && $token ]] && break 1
  done
#  print each element of the array in name="value" shell format
  printf '%s\n' "${paypalReply[@]}"
#  print the token value
  [[ $token ]] && printf '\n\t%s\n' "Token value is: ${token}"
  [[ $receipt ]] && printf '\n\t%s\n' "Receipt id is: ${receipt}"
  printf '\n'
  return 0
}
#------------------------------------------------------------------------------
show_usage() {
  [[ $showUsage ]] || return 0
  errFuncName="$FUNCNAME"
  printf '%b' "
-------------------------------------------------------------------------------

    Usage: ${0##*/} ARGUMENTS

           Send a curl request for a PayPal express checkout payment
           confirmation to ${PAYPAL_NVP_URL}

           Parse and print the \$QUERY_STRING reply to stdout in shell format.

           Print the payment status returned by PayPal to stdout.

Arguments: -a|--amount 0000|0000.00
           Required. The payment amount from the previously sent
           setExpressCheckout request. Must be an integer or decimal value.
           Maximum 2 significant digits.

           -c|--currency $(printf '%s ' "${PAYPAL_CURRENCIES[@]}")
           Required. The ISO currency code of the payment from the previously
           sent setExpressCheckout request. Must be one of the values shown.

           -i|--id IDENTIFIER
           Optional. A unique IDENTIFIER to prevent duplicating a payment
           confirmation. Must be 38 or fewer alphanumeric characters.

           -p|--payer PAYERID
           Required. The PAYERID value returned by PayPal from the previously
           sent setExpressCheckout request. Must be 13 alphanumeric characters.

           -t|--token  TOKEN
           Required. The TOKEN value returned by PayPal from the previously
           sent setExpressCheckout request. Must be 20 characters.

           Missing or invalid arguments cause this message to be displayed.

-------------------------------------------------------------------------------

"
  return 0
}
#------------------------------------------------------------------------------
#  main
#------------------------------------------------------------------------------
main() {
  trap exit_handler EXIT
  argList=("$@")
  [[ ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -ge 2 ]] || \
    [[ ${BASH_VERSINFO[0]} -gt 4 ]] || exit 10
  parse_command_line
  generate_reply
  print_reply
  show_usage
  exit 0
}
#------------------------------------------------------------------------------
#  run program
#------------------------------------------------------------------------------
main "$@"

