diff --git a/config.go b/config.go index a7e1ac3..5bd6f0d 100644 --- a/config.go +++ b/config.go @@ -2,7 +2,6 @@ package main import ( "encoding/json" - "fmt" "io/ioutil" "log" ) @@ -59,6 +58,6 @@ func readConfigFile(file string) error { log.Fatal("cannot print back to json") return err } - fmt.Println(string(j)) + log.Println(string(j)) return err } diff --git a/db.go b/db.go index e3c1b38..6fc8fbf 100644 --- a/db.go +++ b/db.go @@ -21,7 +21,7 @@ func (m *TransactionDB) conn(c AppConfig) error { dbUser := c.DB.User dbPass := c.DB.Pass dbName := c.DB.Schema - h, err := sql.Open(dbDriver, dbUser+":"+dbPass+"@/"+dbName) + h, err := sql.Open(dbDriver, dbUser+":"+dbPass+"@/"+dbName+"?parseTime=true") if err != nil { m.h = nil panic(err.Error()) diff --git a/leanwork_request.go b/leanwork_request.go index c7e833c..13cbf1d 100644 --- a/leanwork_request.go +++ b/leanwork_request.go @@ -2,8 +2,7 @@ package main import ( "errors" - - "github.com/go-sql-driver/mysql" + "time" ) type LeanworkRequest struct { @@ -17,7 +16,7 @@ type LeanworkRequest struct { CustomerId string Sign string Valid bool - Ts mysql.NullTime + Ts time.Time Ip4 uint32 Ip4Location string } diff --git a/rpn.go b/rpn.go index 294d48b..1110ebe 100644 --- a/rpn.go +++ b/rpn.go @@ -1,169 +1,30 @@ package main import ( - "database/sql" - "errors" "fmt" "log" - "math" "net/http" - "net/url" - "strconv" - "time" - - "github.com/go-sql-driver/mysql" ) -type RpnOut struct { - Version string - Sign_type string - Mid string - Notify_url string - Order_amount string - Order_time string //YYYYMMDDHHMMSS - Order_id string - User_id string - User_name string - User_cardno string - Signature string - //template - Url string //where to post entire data structure - //database specific - Id int64 - Leanwork int64 - Ip4 uint32 - Ip4location sql.NullString - Ts mysql.NullTime -} - -// //build request from leanwork request forms -// func (m *RpnOut) buildReqByForm(form url.Values) RpnOut { -// r := RpnOut{} -// r.version = "1.1" -// r.sign_type = "MD5" -// r.mid = "EU85201311P2P" -// r.notify_url = "http://rpn.supertraderfx.ayvwd8em49pvoa3g.com/rpn_notify" -// r.order_id = form["orderNo"][0] -// r.order_amount = form["orderAmount"][0] //cents -// r.order_time = m.now() -// r.user_id = form["customerId"][0] -// r.user_name = "SuperForex" -// r.user_cardno = "6212262002002377849" -// r.signature = md5RpnFormP2P(r) -// *m = r -// return r -// } - -func (m *RpnOut) buildReqByLeanworkRequestP2P(row LeanworkRequest, user_name string, user_cardno string) RpnOut { - - m.Version = "1.1" - m.Sign_type = "MD5" - m.Mid = Config.Rpn.MIDP2P - m.Notify_url = Config.Rpn.UrlCallBack - m.Order_id = row.OrderNo - m.Order_amount = m.translateAmountFromLeanwork(row.OrderAmount) - m.Order_time = m.now() - m.User_id = row.CustomerId - m.User_name = user_name - m.User_cardno = user_cardno - m.Signature = md5RpnFormP2P(*m) - m.Leanwork = row.Id - - return *m -} - -func (m *RpnOut) buildReqByLeanworkRequestFAT(row LeanworkRequest, user_name string, user_cardno string) RpnOut { - m.Version = "1.1" - m.Sign_type = "MD5" - m.Mid = Config.Rpn.MIDFAT - m.Notify_url = Config.Rpn.UrlCallBack - m.Order_id = row.OrderNo - m.Order_amount = m.translateAmountFromLeanwork(row.OrderAmount) - m.Order_time = m.now() - m.User_id = row.CustomerId - m.User_name = user_name - m.User_cardno = user_cardno - m.Signature = md5RpnFormFAT(*m) - m.Leanwork = row.Id - return *m -} - -//from 0.1 to 10 cents -func (m *RpnOut) translateAmountFromLeanwork(from string) string { - f, _ := strconv.ParseFloat(from, 32) - f = f * 100 //convert to cents - t := int(math.Ceil(f)) - s := strconv.Itoa(t) - return s -} - -func (m *RpnOut) now() string { - t := time.Now() - return t.Format("20060102150405") -} - -//send request to RPN to initiate transaction -func (m *RpnOut) SendReq(form url.Values) (*http.Response, error) { - return nil, nil - // r := m.buildReqByForm(form) - // hc := http.Client{Timeout: 15 * time.Second} - - // myForm := url.Values{} - // myForm.Set("version", r.version) - // myForm.Set("sign_type", r.sign_type) - // myForm.Set("mid", r.mid) - // myForm.Set("notify_url", r.notify_url) - // myForm.Set("order_id", r.order_id) - // myForm.Set("order_amount", r.order_amount) - // myForm.Set("order_time", r.order_time) - // myForm.Set("user_id", r.user_id) - // myForm.Set("user_name", r.user_name) - // myForm.Set("user_cardno", r.user_cardno) - // myForm.Set("signature", r.signature) - - // //req, err := http.NewRequest("POST", "https://lawipac.com/dumprequest.php", strings.NewReader(myForm.Encode())) - // req, err := http.NewRequest("POST", Config.Rpn.UrlTest, strings.NewReader(m.encode())) - // if err != nil { - // panic("wrong") - // } - // req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - // req.Header.Add("content-Length", strconv.Itoa(len(form.Encode()))) - // return hc.Do(req) - //Config.Rpn.UrlTest - // return http.PostForm(Config.Rpn.UrlTest, myForm) - //return http.PostForm("https://lawipac.com/dumprequest.php", myForm) - -} - -//encode without disturbing it's original order -func (m *RpnOut) encode() string { - s := "version=" + m.Version - s += "&sign_type=" + m.Sign_type - s += "&mid=" + m.Mid - s += "¬ify_url=" + url.QueryEscape(m.Notify_url) - s += "&order_id=" + m.Order_id - s += "&order_amount=" + m.Order_amount - s += "&order_time=" + m.Order_time - s += "&user_id=" + m.User_id - s += "&user_name=" + url.QueryEscape(m.User_name) - s += "&user_cardno=" + m.User_cardno - s += "&signature=" + m.Signature - return s -} - -func retrieveFormValue(form url.Values, key string) (r string, err error) { +func rpnNotify(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + errPage(w, http.StatusMethodNotAllowed, "invalid request") + return + } + ri, err := GetRpnInFromHTTPRequest(r) //ParseForm called + if err != nil { + errPage(w, http.StatusBadRequest, "invalid parameters") + return + } - if _, ok := form[key]; ok { - r = form[key][0] - err = nil - } else { - r = "" - err = errors.New("Key [" + key + "] not found in HTTP request") + ro, _ := getRpnOutByOrderId(ri.Order_id) + ri.Leanwork = ro.Leanwork + ri1, err := ri.add2db() //TODO:check error add + if err != nil { + log.Printf("failed to add rpnIn %+v , error is %s", ri, err.Error()) } - return r, err -} -func rpnNotify(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "ok notify") + fmt.Fprintf(w, "[SUCCESS]") + return } //receive RPN user name and card number @@ -205,32 +66,3 @@ func rpnNameAndCard(w http.ResponseWriter, r *http.Request) { //build rpn redirect page and send it ro.sendRedirect(w, row) } - -func (m *RpnOut) sendRedirect(w http.ResponseWriter, row LeanworkRequest) { - //execute redirect - m.Url = Config.Rpn.Url - tmpl.ExecuteTemplate(w, "rpnCallOutRedirect", *m) -} - -func getRpnOutByOrderId(order_id string) (ret RpnOut, err error) { - if err = db.conn(Config); err != nil { - return - } - defer db.close() - - q := "SELECT * FROM rpnOut WHERE order_id = ? ORDER BY id DESC LIMIT 1" - err = db.h.QueryRow(q, order_id).Scan( - &ret.Id, &ret.Leanwork, &ret.Version, - &ret.Sign_type, &ret.Mid, &ret.Notify_url, - &ret.Order_amount, &ret.Order_time, &ret.Order_id, - &ret.User_id, &ret.User_name, &ret.User_cardno, &ret.Signature, - &ret.Ip4, &ret.Ip4location, &ret.Ts) - if err != nil { - if err == sql.ErrNoRows { - log.Println("trying to retrieve rpnOut(order_id=" + order_id + ") but not found") - } else { - log.Println("Error retrieving rpnOut(order_id=" + order_id + ") encountered : " + err.Error()) - } - } - return -} diff --git a/rpnIn.go b/rpnIn.go index eb02e73..55ba14b 100644 --- a/rpnIn.go +++ b/rpnIn.go @@ -2,10 +2,11 @@ package main import ( "database/sql" + "errors" "log" + "net/http" "strconv" - - "github.com/go-sql-driver/mysql" + "time" ) type RpnIn struct { @@ -19,7 +20,8 @@ type RpnIn struct { Pay_amount string Pay_result string Signature string - Ts mysql.NullTime + Ts time.Time + Ip4 uint32 } //get given RpnIn record based on ID @@ -34,7 +36,7 @@ func getRpnInById(id int64) (ret RpnIn, err error) { &ret.Id, &ret.Leanwork, &ret.Order_id, &ret.Order_time, &ret.Order_amount, &ret.Deal_id, &ret.Deal_time, &ret.Pay_amount, &ret.Pay_result, - &ret.Signature, &ret.Ts) + &ret.Signature, &ret.Ts, &ret.Ip4) if err != nil { if err == sql.ErrNoRows { log.Println("trying to retrieve rpnIn(" + strconv.FormatInt(id, 10) + ") but not found") @@ -46,27 +48,27 @@ func getRpnInById(id int64) (ret RpnIn, err error) { } //add to database -func (m *RpnIn) add() (ret RpnIn, err error) { +func (m *RpnIn) add2db() (ret RpnIn, err error) { if err = db.conn(Config); err != nil { return } defer db.close() - q := `"INSERT INTO rpnIn( + q := `INSERT INTO rpnIn( leanwork, order_id, order_time, order_amount, deal_id, deal_time, pay_amount, pay_result, signature) VALUES(?,?,?,?,?,?,?,?,?) ` insForm, err := db.h.Prepare(q) if err != nil { - log.Printf("Failed to prepare SQL statment for insert") + log.Printf("Failed to prepare SQL statment for insert " + err.Error()) return } res, err := insForm.Exec( m.Leanwork, m.Order_id, m.Order_time, m.Order_amount, m.Deal_id, m.Deal_time, m.Pay_amount, m.Pay_result, m.Signature) if err != nil { - log.Printf("Error inserting rpnIn with orderNo =%s \n", m.Order_id) + log.Printf("Error inserting rpnIn with orderNo =%s, %s \n", m.Order_id, err.Error()) return } id, err := res.LastInsertId() @@ -75,7 +77,12 @@ func (m *RpnIn) add() (ret RpnIn, err error) { return } - return getRpnInById(id) + ret, err = getRpnInById(id) + if err == nil { + *m = ret + } + + return } func (m *RpnIn) signature() string { @@ -86,6 +93,51 @@ func (m *RpnIn) signature() string { s += "|deal_time=" + m.Deal_time s += "|pay_amount=" + m.Pay_amount s += "|pay_result=" + m.Pay_result + s += "|key=" + m.md5key() + return md5str(s) +} + +func (m *RpnIn) md5key() string { + if m.Order_id == "" { + return "" + } + + ro, err := getRpnOutByOrderId(m.Order_id) + if err != nil { + log.Println("Cannot get RpnOut by order_id=" + m.Order_id) + return "" + } + return ro.getMD5Key() +} - return s +func GetRpnInFromHTTPRequest(r *http.Request) (ret RpnIn, err error) { + r.ParseForm() + ret.Order_id = r.FormValue("order_id") + ret.Order_time = r.FormValue("order_time") + ret.Order_amount = r.FormValue("order_amount") + ret.Deal_id = r.FormValue("deal_id") + ret.Deal_time = r.FormValue("deal_time") + ret.Pay_amount = r.FormValue("pay_amount") + ret.Pay_result = r.FormValue("pay_result") + ret.Signature = r.FormValue("signature") + ret.Ip4 = getClientIPLong(r) + ret.Ts = time.Now() + + if ret.Order_id == "" { + err = errors.New("Invalid OrderId for RpnIn") + return + } + + ro, err := getRpnOutByOrderId(ret.Order_id) + if err != nil || ro.Order_id != ret.Order_id { + log.Println("Cannot get RpnOut by order_id=" + ret.Order_id) + return + } + + if ret.Signature != ret.signature() { + log.Println("Invalid RpnIn Signature") + err = errors.New("Invalid signauture for RpnIn") + return + } + return } diff --git a/rpnIn_test.go b/rpnIn_test.go new file mode 100644 index 0000000..9d877f2 --- /dev/null +++ b/rpnIn_test.go @@ -0,0 +1,52 @@ +package main + +import ( + "io/ioutil" + "net/http" + "net/url" + "testing" + "time" +) + +func TestRpnInCall(t *testing.T) { + form := url.Values{} + ri := RpnIn{} + ri.Order_id = "200310160057TW002184000000000001" + ri.Order_time = time.Now().Format("20060102150405") + ri.Order_amount = "120000" + ri.Deal_id = "deal_id_demo_01" + ri.Deal_time = time.Now().Format("20060102150405") + ri.Pay_amount = "120000" + ri.Pay_result = "3" //3 success, 1 processing + ri.Signature = ri.signature() + + form.Set("order_id", ri.Order_id) + form.Set("order_time", ri.Order_time) + form.Set("order_amount", ri.Order_amount) + form.Set("deal_id", ri.Deal_id) + form.Set("deal_time", ri.Deal_time) + form.Set("pay_amount", ri.Pay_amount) + form.Set("pay_result", ri.Pay_result) + form.Set("signature", ri.Signature) + + resp, err := http.PostForm(Config.Rpn.UrlCallBack, form) + if err != nil { + t.Error("response should have no error") + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Error("response should be OK 200") + } + + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Error("response Body cannot be read") + } + + bodyString := string(bodyBytes) + if bodyString != "[SUCCESS]" { + t.Error("we expect [SUCCESS] but we received:" + bodyString) + } + +} diff --git a/rpnOut.go b/rpnOut.go new file mode 100644 index 0000000..ca7ca42 --- /dev/null +++ b/rpnOut.go @@ -0,0 +1,138 @@ +package main + +import ( + "database/sql" + "log" + "math" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/go-sql-driver/mysql" +) + +type RpnOut struct { + Version string + Sign_type string + Mid string + Notify_url string + Order_amount string + Order_time string //YYYYMMDDHHMMSS + Order_id string + User_id string + User_name string + User_cardno string + Signature string + //template + Url string //where to post entire data structure + //database specific + Id int64 + Leanwork int64 + Ip4 uint32 + Ip4location sql.NullString + Ts mysql.NullTime +} + +func (m *RpnOut) buildReqByLeanworkRequestP2P(row LeanworkRequest, user_name string, user_cardno string) RpnOut { + + m.Version = "1.1" + m.Sign_type = "MD5" + m.Mid = Config.Rpn.MIDP2P + m.Notify_url = Config.Rpn.UrlCallBack + m.Order_id = row.OrderNo + m.Order_amount = m.translateAmountFromLeanwork(row.OrderAmount) + m.Order_time = m.now() + m.User_id = row.CustomerId + m.User_name = user_name + m.User_cardno = user_cardno + m.Signature = md5RpnFormP2P(*m) + m.Leanwork = row.Id + + return *m +} + +func (m *RpnOut) buildReqByLeanworkRequestFAT(row LeanworkRequest, user_name string, user_cardno string) RpnOut { + m.Version = "1.1" + m.Sign_type = "MD5" + m.Mid = Config.Rpn.MIDFAT + m.Notify_url = Config.Rpn.UrlCallBack + m.Order_id = row.OrderNo + m.Order_amount = m.translateAmountFromLeanwork(row.OrderAmount) + m.Order_time = m.now() + m.User_id = row.CustomerId + m.User_name = user_name + m.User_cardno = user_cardno + m.Signature = md5RpnFormFAT(*m) + m.Leanwork = row.Id + return *m +} + +//from 0.1 to 10 cents +func (m *RpnOut) translateAmountFromLeanwork(from string) string { + f, _ := strconv.ParseFloat(from, 32) + f = f * 100 //convert to cents + t := int(math.Ceil(f)) + s := strconv.Itoa(t) + return s +} + +func (m *RpnOut) now() string { + t := time.Now() + return t.Format("20060102150405") +} + +//encode without disturbing it's original order +func (m *RpnOut) encode() string { + s := "version=" + m.Version + s += "&sign_type=" + m.Sign_type + s += "&mid=" + m.Mid + s += "¬ify_url=" + url.QueryEscape(m.Notify_url) + s += "&order_id=" + m.Order_id + s += "&order_amount=" + m.Order_amount + s += "&order_time=" + m.Order_time + s += "&user_id=" + m.User_id + s += "&user_name=" + url.QueryEscape(m.User_name) + s += "&user_cardno=" + m.User_cardno + s += "&signature=" + m.Signature + return s +} + +func (m *RpnOut) sendRedirect(w http.ResponseWriter, row LeanworkRequest) { + //execute redirect + m.Url = Config.Rpn.Url + tmpl.ExecuteTemplate(w, "rpnCallOutRedirect", *m) +} + +func (m *RpnOut) getMD5Key() string { + if m.Mid == Config.Rpn.MIDP2P { + return Config.Rpn.MD5P2P + } else if m.Mid == Config.Rpn.MD5FAT { + return Config.Rpn.MD5FAT + } else { + return "" + } +} + +func getRpnOutByOrderId(order_id string) (ret RpnOut, err error) { + if err = db.conn(Config); err != nil { + return + } + defer db.close() + + q := "SELECT * FROM rpnOut WHERE order_id = ? ORDER BY id DESC LIMIT 1" + err = db.h.QueryRow(q, order_id).Scan( + &ret.Id, &ret.Leanwork, &ret.Version, + &ret.Sign_type, &ret.Mid, &ret.Notify_url, + &ret.Order_amount, &ret.Order_time, &ret.Order_id, + &ret.User_id, &ret.User_name, &ret.User_cardno, &ret.Signature, + &ret.Ip4, &ret.Ip4location, &ret.Ts) + if err != nil { + if err == sql.ErrNoRows { + log.Println("trying to retrieve rpnOut(order_id=" + order_id + ") but not found") + } else { + log.Println("Error retrieving rpnOut(order_id=" + order_id + ") encountered : " + err.Error()) + } + } + return +}