Skip to content

Instantly share code, notes, and snippets.

@mendsley
Created February 11, 2014 19:36
Show Gist options
  • Save mendsley/8942447 to your computer and use it in GitHub Desktop.
Save mendsley/8942447 to your computer and use it in GitHub Desktop.
/*
* Copyright 2011 Matthew Endsley. All rights reserved.
* Copyright 2012 Carbon Games, Inv. All rights reserved.
*/
package billing
import (
"carbon/keys"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
)
type paypalCredentials struct {
user string
pass string
sig string
}
func paypalAddCredentials(micro bool, sandbox bool) paypalCredentials {
if sandbox {
return paypalCredentials{user: keys.PaypalSandboxUser, pass: keys.PaypalSandboxPass, sig: keys.PaypalSandboxSig}
}
if micro {
return paypalCredentials{user: keys.PaypalMicroUser, pass: keys.PaypalMicroPass, sig: keys.PaypalMicroSig}
}
return paypalCredentials{user: keys.PaypalUser, pass: keys.PaypalPass, sig: keys.PaypalSig}
}
func paypalApiUrl(sandbox bool) string {
if sandbox {
return "https://api-3t.sandbox.paypal.com/nvp"
}
return "https://api-3t.paypal.com/nvp"
}
type paypalAPIData struct {
params url.Values
sandbox bool
micro bool
}
func paypalAPICall(data paypalAPIData) (url.Values, error) {
creds := paypalAddCredentials(data.micro, data.sandbox)
data.params.Add("USER", creds.user)
data.params.Add("PWD", creds.pass)
data.params.Add("SIGNATURE", creds.sig)
response, err := http.PostForm(paypalApiUrl(data.sandbox), data.params)
if err != nil {
return nil, errors.New("Failed to contact paypal: " + err.Error())
}
defer response.Body.Close()
bodyData, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, errors.New("Failed to read response from paypal: " + err.Error())
}
resultParams, err := url.ParseQuery(string(bodyData))
if err != nil {
return nil, errors.New("Failed to parse response from paypal: " + err.Error())
}
if resultParams.Get("ACK") != "Success" {
return nil, errors.New("Paypal request " + data.params.Get("METHOD") + " returned error " + resultParams.Get("L_ERRORCODE0") + ": " + resultParams.Get("L_LONGMESSAGE0"))
}
return resultParams, nil
}
func paypalGetPurchaseData(purchase *purchaseRequest, txnId TransactionId, _ map[string]string) (BillingData, error) {
p := make(url.Values)
p.Add("METHOD", "SetExpressCheckout")
p.Add("VERSION", "86")
p.Add("RETURNURL", "https://www.carbongames.com/purchaseComplete.html")
p.Add("CANCELURL", "https://www.carbongames.com/purchaseCancel.html")
p.Add("NOSHIPPING", "1")
p.Add("REQCONFIRMSHIPPING", "0")
p.Add("PAYMENTREQUEST_0_AMT", purchase.Price)
p.Add("PAYMENTREQUEST_0_CURRENCYCODE", purchase.Currency)
p.Add("PAYMENTREQUEST_0_ITEMAMT", purchase.Price)
p.Add("PAYMENTREQUEST_0_ITEMDESC", purchase.Name)
p.Add("PAYMENTREQUEST_0_INVNUM", strconv.Itoa(txnId.ToInteger()))
p.Add("PAYMENTREQUEST_0_ALLOWEDPAYMENTMETHOD", "InstantPaymentOnly")
p.Add("PAYMENTREQUEST_0_PAYMENTACTION", "Sale")
p.Add("L_PAYMENTREQUEST_0_NAME0", purchase.Name)
p.Add("L_PAYMENTREQUEST_0_DESC0", purchase.Description)
p.Add("L_PAYMENTREQUEST_0_AMT0", purchase.Price)
p.Add("L_PAYMENTREQUEST_0_QTY0", "1")
p.Add("L_PAYMENTREQUEST_0_ITEMCATEGORY0", "Digital")
dollars := 0
cents := 0
_, err := fmt.Sscanf(purchase.Price, "%d.%d", &dollars, &cents)
if err != nil {
cents = 0
dollars, _ = strconv.Atoi(purchase.Price)
}
cents += 100 * dollars
micro := (cents < 1244)
responseParams, err := paypalAPICall(paypalAPIData{params: p, sandbox: purchase.Sandbox, micro: micro})
if err != nil {
return nil, err
}
token := responseParams.Get("TOKEN")
if token == "" {
return nil, errors.New("Failed to get a purchase token from paypal.")
}
sandboxToken := ""
if purchase.Sandbox {
sandboxToken = ".sandbox"
}
url := "https://www" + sandboxToken + ".paypal.com/cgi-bin/webscr?cmd=_express-checkout&useraction=commit&token=" + token
return map[string]interface{}{"token": token, "url": url}, nil
}
func FinalizePaypalOrder(orderToken string, requestingId uint64) error {
p := make(url.Values)
p.Add("METHOD", "GetExpressCheckoutDetails")
p.Add("VERSION", "86")
p.Add("TOKEN", orderToken)
sandbox := false
micro := false
responseParams, err := paypalAPICall(paypalAPIData{params: p, sandbox: false, micro: false})
if err != nil {
micro = true
responseParams, err = paypalAPICall(paypalAPIData{params: p, sandbox: false, micro: true})
if err != nil {
sandbox = true
var err2 error
responseParams, err2 = paypalAPICall(paypalAPIData{params: p, sandbox: true})
if err2 != nil {
return errors.New(fmt.Sprint("Failed to finalize order for ", requestingId, ", couldn't lookup order details from paypal: ", err.Error(), "\n\tsandbox error: ", err2.Error()))
}
}
}
checkoutStatus := responseParams.Get("CHECKOUTSTATUS")
payerId := responseParams.Get("PAYERID")
transactionString := responseParams.Get("PAYMENTREQUEST_0_INVNUM")
total := responseParams.Get("PAYMENTREQUEST_0_AMT")
currencyCode := responseParams.Get("PAYMENTREQUEST_0_CURRENCYCODE")
if payerId == "" || transactionString == "" || total == "" || currencyCode == "" {
return errors.New(fmt.Sprint("Aborting paypal transaction for ", requestingId, " as paypal failed to return some data to us - CheckoutStatus: ", checkoutStatus))
}
txnInteger, err := strconv.Atoi(transactionString)
if err != nil {
return errors.New(fmt.Sprint("Failed to parse transaction id from paypal: ", err.Error()))
}
txnId := TxnFromInteger(txnInteger)
if IsTransactionComplete(txnId.String()) {
return errors.New(fmt.Sprint("Ignoring request to finalize paypal transaction a second time for ", requestingId, " transaction: ", txnId.String()))
}
// Charge the user
p = make(url.Values)
p.Add("METHOD", "DoExpressCheckoutPayment")
p.Add("VERSION", "86")
p.Add("TOKEN", orderToken)
p.Add("PAYERID", payerId)
p.Add("PAYMENTREQUEST_0_PAYMENTACTION", "SALE")
p.Add("PAYMENTREQUEST_0_AMT", total)
p.Add("PAYMENTREQUEST_0_CURRENCYCODE", currencyCode)
copyParam := func(str string) {
p.Add(str, responseParams.Get(str))
}
copyParam("PAYMENTREQUEST_0_ITEMAMT")
copyParam("PAYMENTREQUEST_0_ITEMDESC")
copyParam("PAYMENTREQUEST_0_INVNUM")
copyParam("L_PAYMENTREQUEST_0_NAME0")
copyParam("L_PAYMENTREQUEST_0_DESC0")
copyParam("L_PAYMENTREQUEST_0_AMT0")
copyParam("L_PAYMENTREQUEST_0_QTY0")
responseParams, err = paypalAPICall(paypalAPIData{params: p, sandbox: sandbox, micro: micro})
if err != nil {
return errors.New(fmt.Sprint("Failed to charge user ", requestingId, " for paypal order ", txnId.String(), ": ", err.Error()))
}
var completion purchaseCompletion
completion.provider = "paypal"
completion.sandbox = sandbox
completion.currency = currencyCode
completion.price = total
completion.txnString = txnId.String()
completion.providerOrder = responseParams.Get("PAYMENTINFO_0_TRANSACTIONID")
ch := make(chan int)
go func() {
purchaseProcessTransaction(&completion)
ch <- 0
}()
<-ch
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment