Created
February 11, 2014 19:36
-
-
Save mendsley/8942447 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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, ¢s) | |
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