Golang : Interfacing with PayPal's IPN(Instant Payment Notification) example
A startup or any online company at minimum needs to have 3 people. One to make something, another to sell something and finally one to collect payment for having selling something. This tutorial is about collecting payment via PayPal.
PayPal has been a standard for many startups to accept payment online ( Stripe is becoming more popular these days, but they are not available worldwide yet) and this tutorial is on how to configure your Golang program to interface with PayPal's IPN(Instant Payment Notification).
Below is an example how to interface with PayPal's IPN in Golang :
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strings"
)
// PayPal variables -- change it according to your requirements.
// NOTE : I decided to use individual variables here so that I can include
// explanations. It would be better and "cleaner" to put these variables
// into array(such as url.Values{} https://golang.org/pkg/net/url/#Values ) and
// loop the array for name and values in the form hidden fields below.
var currency_code = "USD"
var business = "[your email address at PayPal to receive money]" //PayPal account to receive money
var image_url = "https://d1ohg4ss876yi2.cloudfront.net/logo35x35.png" // image on top of PayPal
// change socketloop.com:8080 to your domain name
// REMEMBER : localhost won't work and IPN simulator only deal with port 80 or 443
var cancel_return = "http://[your domain]/paymentcancelreturn"
var return_url = "http://[your domain]/paymentsuccess" // return is Golang's keyword
var notify_url = "http://[your domain]/ipn" // <--- important for IPN to work!
// just an example for custom field, could be username, etc. Use custom field
// for extra verification purpose or to mark PAID status in
// in database, etc.
var custom = "donation"
// See
// https://developer.paypal.com/docs/classic/paypal-payments-standard/integration-guide/Appx_websitestandard_htmlvariables/
// for the meaning of rm and _xclick
var rm = "2" // rm 2 equal Return method = POST
var cmd = "_xclick"
var item_name = "Donation for SocketLoop"
var quantity = "1"
var amount = "5" // keeping it simple for this tutorial. You should accept the amount
// from a form instead of hard coding it here.
// uncomment to switch to real PayPal instead of sandbox
//var paypal_url = "https://www.paypal.com/cgi-bin/webscr"
var paypal_url = "https://www.sandbox.paypal.com/cgi-bin/webscr"
func Home(w http.ResponseWriter, r *http.Request) {
html := "<html><body><h1>You will be directed to PayPal now to pay USD " + amount + " to SocketLoop!</h1>"
html = html + "<form action=' " + paypal_url + "' method='post'>"
// now add the PayPal variables to be posted
// a cleaner way to create an array and use for loop
html = html + "<input type='hidden' name='currency_code' value='" + currency_code + "'>"
html = html + "<input type='hidden' name='business' value='" + business + "'>"
html = html + "<input type='hidden' name='image_url' value='" + image_url + "'>"
html = html + "<input type='hidden' name='cancel_return' value='" + cancel_return + "'>"
html = html + "<input type='hidden' name='notify_url' value='" + notify_url + "'>"
html = html + "<input type='hidden' name='return' value='" + return_url + "'>" //use return instead of return_url
html = html + "<input type='hidden' name='custom' value='" + custom + "'>"
html = html + "<input type='hidden' name='rm' value='" + rm + "'>"
html = html + "<input type='hidden' name='cmd' value='" + cmd + "'>"
html = html + "<input type='hidden' name='item_name' value='" + item_name + "'>"
html = html + "<input type='hidden' name='quantity' value='" + quantity + "'>"
html = html + "<input type='hidden' name='amount' value='" + amount + "'>"
html = html + " <input type='submit' value='Proceed to PayPal'></form></body></html>"
w.Write([]byte(fmt.Sprintf(html)))
}
func PaymentSuccess(w http.ResponseWriter, r *http.Request) {
// This is where you would probably want to thank the user for their order
// or what have you. The order information at this point is in POST
// variables. However, you don't want to "process" the order until you
// get validation from the IPN. That's where you would have the code to
// email an admin, update the database with payment status, activate a
// membership, etc.
html := "<html><body><h1>Thank you! Payment accepted!</h1></body></html>"
w.Write([]byte(fmt.Sprintf(html)))
}
func PaymentCancelReturn(w http.ResponseWriter, r *http.Request) {
html := "<html><body><h1>Oh ok. Payment cancelled!</h1></body></html>"
w.Write([]byte(fmt.Sprintf(html)))
}
func IPN(w http.ResponseWriter, r *http.Request) {
// Payment has been received and IPN is verified. This is where you
// update your database to activate or process the order, or setup
// the database with the user's order details, email an administrator,
// etc. You can access a slew of information via the IPN data from r.Form
// Check the paypal documentation for specifics on what information
// is available in the IPN POST variables. Basically, all the POST vars
// which paypal sends, which we send back for validation.
// For this tutorial, we'll just print out all the IPN data.
fmt.Println("IPN received from PayPal")
err := r.ParseForm() // need this to get PayPal's HTTP POST of IPN data
if err != nil {
fmt.Println(err)
return
}
if r.Method == "POST" {
var postStr string = paypal_url + "&cmd=_notify-validate&"
for k, v := range r.Form {
fmt.Println("key :", k)
fmt.Println("value :", strings.Join(v, ""))
// NOTE : Store the IPN data k,v into a slice. It will be useful for database entry later.
postStr = postStr + k + "=" + url.QueryEscape(strings.Join(v, "")) + "&"
}
// To verify the message from PayPal, we must send
// back the contents in the exact order they were received and precede it with
// the command _notify-validate
// PayPal will then send one single-word message, either VERIFIED,
// if the message is valid, or INVALID if the messages is not valid.
// See more at
// https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNIntro/
// post data back to PayPal
client := &http.Client{}
req, err := http.NewRequest("POST", postStr, nil)
if err != nil {
fmt.Println(err)
return
}
req.Header.Add("Content-Type: ", "application/x-www-form-urlencoded")
// fmt.Println(req)
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Response : ")
fmt.Println(resp)
fmt.Println("Status :")
fmt.Println(resp.Status)
// convert response to string
respStr, _ := ioutil.ReadAll(resp.Body)
//fmt.Println("Response String : ", string(respStr))
verified, err := regexp.MatchString("VERIFIED", string(respStr))
if err != nil {
fmt.Println(err)
return
}
if verified {
fmt.Println("IPN verified")
fmt.Println("TODO : Email receipt, increase credit, etc")
} else {
fmt.Println("IPN validation failed!")
fmt.Println("Do not send the stuff out yet!")
}
}
}
func main() {
// http.Handler
mux := http.NewServeMux()
mux.HandleFunc("/", Home)
mux.HandleFunc("/paymentcancelreturn", PaymentCancelReturn) // remember, case sensitive
mux.HandleFunc("/paymentsuccess", PaymentSuccess) // remember, case sensitive
mux.HandleFunc("/ipn", IPN)
http.ListenAndServe(":8080", mux)
}
Change the domain name to yours and run the code.
Point your browser to the main URL will show you this page :
and after clicking the button :
follow by after payment :
finally, after the IPN message was posted back to PayPal and verified :
r.Form
contains useful data from IPN. Use the data for extra verification purpose, such as making sure the amount is the same before posting to PayPal. Email or username ( via custom field ) is the same before posting to PayPal. These extra security step are meant to make sure that no data changed(spoofing) during postings.
References :
https://github.com/paypal/ipn-code-samples/blob/master/paypal_ipn.pl
https://github.com/asadovsky/tadue/blob/master/app/paypal.go
https://www.socketloop.com/tutorials/golang-parsing-or-breaking-down-url
By Adam Ng
IF you gain some knowledge or the information here solved your programming problem. Please consider donating to the less fortunate or some charities that you like. Apart from donation, planting trees, volunteering or reducing your carbon footprint will be great too.
Advertisement
Tutorials
+9.7k Golang : Setting variable value with ldflags
+9.4k Golang : Copy map(hash table) example
+19.5k Golang : Archive directory with tar and gzip
+8.8k Golang : How to capture return values from goroutines?
+15k Golang : Find location by IP address and display with Google Map
+9.8k CodeIgniter : Load different view for mobile devices
+17.2k Golang : Clone with pointer and modify value
+23.8k Golang : Find biggest/largest number in array
+18.4k Golang : Implement getters and setters
+6.3k Golang : How to determine if request or crawl is from Google robots
+6.8k Golang : How to call function inside template with template.FuncMap