package main import ( "fmt" "strconv" "net/http" "encoding/json" "github.com/gorilla/mux" "github.com/patrickmn/go-cache" ) // // https://stripe.com/docs/api#create_refund-charge // func RefundsHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("RefundsHandler : INIT") //build the form r.ParseForm() fmt.Println("-----") fmt.Println(r.Form) fmt.Println("-----") httpStatus := http.StatusBadRequest //capture id vars := mux.Vars(r) captureId := vars["id"] // // Set all Headers // header := w.Header() requestId := CreateRequestId() header.Set("content-type", "application/json") header.Set("stripe-version", "mock-1.0") header.Set("request-id", requestId) header.Set("original-capture-id", captureId) //copy the idempotency key to response idempotencyKey := r.Header.Get("idempotency-key") header.Set("idempotency-key", idempotencyKey) //make hash of form this will help maintain idempotency formHash := MD5Hash(r.Form) header.Set("request-md5", formHash) // // check for auth idempotency_key // idempotencyObj, found := idempotencyCache.Get(idempotencyKey) //found if found { fmt.Println("RefundsHandler : idempotency found for key: " + idempotencyKey) //response object errorObjects := ErrorResponse{ Error: ErrorObject{ Type: "idempotency_error", }, } // idempotency := idempotencyObj.(Idempotency) // exit := true if idempotency.Type == "auth" { fmt.Println("RefundsHandler : idempotency auth key found") //end user is trying to access capture with same idempotency as of auth. errorObjects.Error.Message = "Keys for idempotent requests can only be used for the same endpoint they were first used for ('/v1/charges/" + captureId + "/refunds' vs '/v1/charges'). Try using a key other than '" + idempotencyKey + "' if you meant to execute a different request." } else if idempotency.Type == "capture" { fmt.Println("RefundsHandler : idempotency capture key found") //end user is trying to access capture with same idempotency as of capture. errorObjects.Error.Message = "Keys for idempotent requests can only be used for the same endpoint they were first used for ('/v1/charges/" + captureId + "/refunds' vs '/v1/charges/" + captureId + "/capture'). Try using a key other than '" + idempotencyKey + "' if you meant to execute a different request." } else if idempotency.Type == "void" && idempotency.RequestHash != formHash { fmt.Println("RefundsHandler : idempotency void key found with md5:" + formHash) //end user is trying to access capture with same idempotency as of void with different form parameters. errorObjects.Error.Message = "Keys for idempotent requests can only be used with the same parameters they were first used with. Try using a key other than '" + idempotencyKey + "' if you meant to execute a different request." } else { //valid request lets process fmt.Println("RefundsHandler : idempotency auth key cache") exit = false } // idempotency error so exit if exit { fmt.Fprintln(w, json.NewEncoder(w).Encode(errorObjects)) //final http status code w.WriteHeader(httpStatus) return } } else { //new request fmt.Println("RefundsHandler : new request with idempotency key: " + idempotencyKey) idempotency := Idempotency{ Type: "void", RequestId: requestId, ChargeId: captureId, RequestHash: formHash, } //set cache for next use idempotencyCache.Set(idempotencyKey, idempotency, cache.DefaultExpiration) } // //check for cached void object // cachedObj, found := voidCache.Get(captureId) if found { fmt.Println("RefundsHandler : cache fault:" + idempotencyKey) //get capture object from cache cacheObject := cachedObj.(CacheObject) //copy original request id header.Set("original-request", cacheObject.RequestId) //write to stream if cacheObject.Status == 200 { json.NewEncoder(w).Encode(cacheObject.Refund) } else { //this will never happen json.NewEncoder(w).Encode(cacheObject.Error) } //should be the last w.WriteHeader(cacheObject.Status) return } // // First time request // fmt.Println("RefundsHandler :First time request") //original request id and request id will be same this case header.Set("original-request", requestId) //evaluate auth & capture cachedObj, found = captureCache.Get(captureId) flow := "Auth" // if !found { flow = "Capture" //void auth before capture cachedObj, found = chargeCache.Get(captureId) } // // all set // if found { fmt.Println("RefundsHandler : " + flow + " found for " + captureId) //process chargeObject := (cachedObj.(CacheObject)).Charge // reqAmount, err := strconv.Atoi(FindFist(r.Form["amount"])) reqReason := FindFist(r.Form["reason"]) // fmt.Println("RefundsHandler : Start", err) // if reqAmount <= 0 { //charge amount should not be less than requested amount errorObjects := ErrorResponse{ Error: ErrorObject{ Type: "invalid_request_error", Message: "Amount must be at least 50 cents.", Param: "amount", }, } //write to stream json.NewEncoder(w).Encode(errorObjects) } else if chargeObject.Amount < (chargeObject.AmountRefunded + reqAmount) { //charge amount should be greater than requested amount errorObjects := ErrorResponse{ Error: ErrorObject{ Type: "invalid_request_error", Message: "You cannot partially refund an uncaptured charge. Instead, capture the charge for an amount less than the original amount", Param: "amount", }, } //write to stream json.NewEncoder(w).Encode(errorObjects) } else if reqReason != "" && reqReason != "duplicate" && reqReason != "fraudulent" && reqReason != "requested_by_customer" { //reason is an enum errorObjects := ErrorResponse{ Error: ErrorObject{ Type: "invalid_request_error", Message: "Invalid reason: must be one of duplicate, fraudulent, or requested_by_customer", Param: "reason", }, } //write to stream json.NewEncoder(w).Encode(errorObjects) } else { //refund is posible only upto refund amount refundId := "txn_" + CreateChargeId() chargeObject.Captured = true //set refund object refundData := RefundData{ ID: "re_" + CreateChargeId(), Object: "refund", Amount: reqAmount, BalanceTransaction: refundId, Charge: captureId, Created: Timestamp(), Currency: chargeObject.Currency, Status: "succeeded", } //success-write to stream json.NewEncoder(w).Encode(refundData) //status ok httpStatus = http.StatusOK //put object into cache cacheableObject := CacheObject{ Status: httpStatus, RequestId: requestId, Refund: refundData, Idempotency: idempotencyKey, } //cache item for next use voidCache.Set(captureId, cacheableObject, cache.DefaultExpiration) //print fmt.Println("RefundsHandler : mock object created") } } else { //end user is trying to access service with the same request format errorObjects := ErrorResponse{ Error: ErrorObject{ Type: "invalid_request_error", Message: "No such charge: " + captureId, Param: "id", }, } //write error message fmt.Fprintln(w, json.NewEncoder(w).Encode(errorObjects)) // fmt.Println("RefundsHandler : No such charge: " + captureId) } //write http status w.WriteHeader(httpStatus) }