From: "Hal E. Fulton" Date: 2002-11-27T12:27:58+09:00 Subject: PayPal developers here? Has anyone here ever dealt with PayPal development, e.g., scripts called by their IPN (Instant Payment Notification)? With the help of a couple of people, I ported the Perl script to Ruby... but I haven't got it to work. Code below, if you're curious. Hal require 'cgi' require 'net/http' ###################### Change as needed ###################### PASSWORD_FILE = '' # path of .htpassword file TRANSACTION_FILE = '' # Path of processed_txns file PERIOD1 = nil # Initial trial period; ex "1 M" = 1 month PERIOD2 = nil # Second trial period PERIOD3 = nil # Recurring/normal period AMOUNT1 = nil # Dollar amts for each period (free = 0.00) AMOUNT2 = nil AMOUNT3 = nil SENDMAIL_PATH = '/usr/sbin/sendmail' # nil if none ADMIN_EMAIL = nil # For error notifications; nil if none PAYMENT_EMAILS = [''] # Primary Paypal email addr # change if running with https PAYPAL_URL0 = 'www.paypal.com' PAYPAL_URL1 = '/cgi-bin/webscr' ############################################################## NONFATAL = NO_ACTION = false FATAL = ACTION = true ### def warn(str) $stderr.puts str end def die(str, code=1) $stderr.puts str exit(code) end def param(key) rep = $cgi.params[key] and rep[0] end ### class File def self.safe_operation(filename, open_str) file = File.open(filename, open_str) file.flock(File::LOCK_SH) file.rewind yield file rescue notify("Unable to access: #{filename} - #{$!}\n", "open file") ensure file.close if file end def find(regexp) each {|line| return true if regexp =~ line } false end def seek_end seek(0, IO::SEEK_END) end end ### def respond(success=true) # Handle http reponse no_content = {"status" => "204 No Content", "type" => "text/plain"} server_error = {"status" => "SERVER_ERROR", "type" => "text/plain"} if success puts CGI.new.header(no_content) else CGI.new.out(server_error) { "Internal server error\n" } end end def ack_ipn # ack the ipn begin h = Net::HTTP.new(PAYPAL_URL0, 80) resp, = h.post(PAYPAL_URL1, ENV["QUERY_STRING"] + "&cmd=_notify-validate") rescue => err notify("Could not acknowledge because of a network " + "(or Paypal-related) problem. \nWill retry until success.\n" + "Error was: #{err}", "acknowledge IPN", FATAL, NO_ACTION) end case resp.body when /VERIFIED/ return true when /INVALID/ notify("Notification not from PayPal; message ignored.", "acknowledge IPN", NONFATAL, NO_ACTION) end false end def validate_signup # validate the terms and amounts if param("period1") != PERIOD1 || param("period2") != PERIOD2 || param("period3") != PERIOD3 || param("amount1") != AMOUNT1 || param("amount2") != AMOUNT2 || param("amount3") != AMOUNT3 notify("Customer did not sign up according to your payment terms. " + "Although payment was accepted, the account was not activated.", "validate subscription terms", NONFATAL) return false end # validate the receiver email if ! PAYMENT_EMAILS.include? param("receiver_email") notify("The IPN received did not match your primary email.\n" + "Message ignored.", "validate receiver email ", NONFATAL, NO_ACTION) return false end File.safe_operation(TRANSACTION_FILE, "r+") do |file| if file.find(/\A#{param("subscr_id")}/) notify("The IPN received was previously processed.\n" + "Message ignored.", "validate subscription id", NONFATAL, NO_ACTION) return false end end true end ### def notify_no_user(action) notify("No username found. Check subscription button or link.", action, NONFATAL) end def handle_ipn case param("txn_type") when "subscr_signup" param("username") ? add_user : notify_no_user("add user") when "subscr_eot" param("username") ? remove_user : notify_no_user("remove user") else # do nothing end end ### def sendmail(mail) # send email using sendmail begin IO.popen("#{SENDMAIL_PATH} -t", "w") do |io| io.puts <<-EOF To: #{mail['To']} From: #{mail['From']} Subject: #{mail['Subject']} Content-type: text/plain #{mail['Message']} EOF end rescue => err warn "sendmail error: #{err}" end end ### Send email notification of error def notify(err,action,fatal=true,attention=true) message = <<-EOS Error occurred while trying to #{action}: #{err} User Information Subscriber username: #{param("username")} Subscriber email: #{param("payer_email")} Subscription number: #{param("subscr_id")} Transaction type: #{param("txn_type")} EOS subject = "Subscription Error - " subject += attention ? "Requires action" : "No action required" # If no email, log only if ADMIN_EMAIL and SENDMAIL_PATH mail = {"To"=>ADMIN_EMAIL, "From"=>ADMIN_EMAIL, "Subject"=>subject, "Message"=>message} sendmail(mail) end # Log error if fatal respond(false) # IPN will retry die message else warn message end end ### def add_user login = param("username") password = param("password") txn = param("subscr_id") File.safe_operation(PASSWORD_FILE, "r+") do |file| if !file.find(/\A#{login}:/) file.seek_end file.puts "#{login}:#{password}" end end File.safe_operation(TRANSACTION_FILE, "r+") do |file| file.seek_end file.puts txn end end ### def remove_user login = param("username") File.safe_operation(PASSWORD_FILE, "r+") do |file| others = [] file.each {|line| others.push(line) if ! /\A#{login}:/ } unless others.empty? file.rewind file.puts others file.truncate(file.tell) end end end ### # "main" $cgi = CGI.new handle_ipn if ack_ipn # Acknowlege IPN from PayPal respond # Success