From 1be8989df5dc6ca516890860b1d31bd449b90537 Mon Sep 17 00:00:00 2001 From: Nic West Date: Tue, 25 Oct 2016 20:44:10 +0100 Subject: [PATCH] add web experience profile endpoints --- types.go | 65 ++++++++ webprofile.go | 120 ++++++++++++++ webprofile_test.go | 399 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 584 insertions(+) create mode 100644 webprofile.go create mode 100644 webprofile_test.go diff --git a/types.go b/types.go index 2b33fb2..392f001 100644 --- a/types.go +++ b/types.go @@ -18,6 +18,31 @@ const ( RequestNewTokenBeforeExpiresIn = 60 ) +// Possible values for `no_shipping` in InputFields +// +// https://developer.paypal.com/docs/api/payment-experience/#definition-input_fields +const ( + NoShippingDisplay uint = 0 + NoShippingHide uint = 1 + NoShippingBuyerAccount uint = 2 +) + +// Possible values for `address_override` in InputFields +// +// https://developer.paypal.com/docs/api/payment-experience/#definition-input_fields +const ( + AddrOverrideFromFile uint = 0 + AddrOverrideFromCall uint = 1 +) + +// Possible values for `landing_page_type` in FlowConfig +// +// https://developer.paypal.com/docs/api/payment-experience/#definition-flow_config +const ( + LandingPageTypeBilling string = "Billing" + LandingPageTypeLogin string = "Login" +) + type ( // Address struct Address struct { @@ -361,6 +386,46 @@ type ( AgeRange string `json:"age_range,omitempty"` PayerID string `json:"payer_id,omitempty"` } + + // WebProfile represents the configuration of the payment web payment experience + // + // https://developer.paypal.com/docs/api/payment-experience/ + WebProfile struct { + ID string `json:"id,omitempty"` + Name string `json:"name"` + Presentation Presentation `json:"presentation,omitempty"` + InputFields InputFields `json:"input_fields,omitempty"` + FlowConfig FlowConfig `json:"flow_config,omitempty"` + } + + // Presentation represents the branding and locale that a customer sees on + // redirect payments + // + // https://developer.paypal.com/docs/api/payment-experience/#definition-presentation + Presentation struct { + BrandName string `json:"brand_name,omitempty"` + LogoImage string `json:"logo_image,omitempty"` + LocaleCode string `json:"locale_code,omitempty"` + } + + // InputFields represents the fields that are displayed to a customer on + // redirect payments + // + // https://developer.paypal.com/docs/api/payment-experience/#definition-input_fields + InputFields struct { + AllowNote bool `json:"allow_note,omitempty"` + NoShipping uint `json:"no_shipping,omitempty"` + AddressOverride uint `json:"address_override,omitempty"` + } + + // FlowConfig represents the general behaviour of redirect payment pages + // + // https://developer.paypal.com/docs/api/payment-experience/#definition-flow_config + FlowConfig struct { + LandingPageType string `json:"landing_page_type,omitempty"` + BankTXNPendingURL string `json:"bank_txn_pending_url,omitempty"` + UserAction string `json:"user_action,omitempty"` + } ) // Error method implementation for ErrorResponse struct diff --git a/webprofile.go b/webprofile.go new file mode 100644 index 0000000..4f0222e --- /dev/null +++ b/webprofile.go @@ -0,0 +1,120 @@ +package paypalsdk + +import ( + "fmt" + "net/http" +) + +// CreateWebProfile creates a new web experience profile in Paypal +// +// Allows for the customisation of the payment experience +// +// Endpoint: POST /v1/payment-experience/web-profiles +func (c *Client) CreateWebProfile(wp WebProfile) (*WebProfile, error) { + url := fmt.Sprintf("%s%s", c.APIBase, "/v1/payment-experience/web-profiles") + req, err := c.NewRequest(http.MethodPost, url, wp) + if err != nil { + return &WebProfile{}, err + } + + response := &WebProfile{} + + err = c.SendWithAuth(req, response) + if err != nil { + return response, err + } + + return response, nil +} + +// GetWebProfile gets an exists payment experience from Paypal +// +// Endpoint: GET /v1/payment-experience/web-profiles/ +func (c *Client) GetWebProfile(profileID string) (*WebProfile, error) { + var wp WebProfile + + url := fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payment-experience/web-profiles/", profileID) + req, err := http.NewRequest(http.MethodGet, url, nil) + + if err != nil { + return &wp, err + } + + err = c.SendWithAuth(req, &wp) + if err != nil { + return &wp, err + } + + if wp.ID == "" { + return &wp, fmt.Errorf("paypalsdk: unable to get web profile with ID = %s", profileID) + } + + return &wp, nil +} + +// GetWebProfiles retreieves web experience profiles from Paypal +// +// Endpoint: GET /v1/payment-experience/web-profiles +func (c *Client) GetWebProfiles() ([]WebProfile, error) { + var wps []WebProfile + + url := fmt.Sprintf("%s%s", c.APIBase, "/v1/payment-experience/web-profiles") + req, err := http.NewRequest(http.MethodGet, url, nil) + + if err != nil { + return wps, err + } + + err = c.SendWithAuth(req, &wps) + if err != nil { + return wps, err + } + + return wps, nil +} + +// SetWebProfile sets a web experience profile in Paypal with given id +// +// Endpoint: PUT /v1/payment-experience/web-profiles +func (c *Client) SetWebProfile(wp WebProfile) error { + + if wp.ID == "" { + return fmt.Errorf("paypalsdk: no ID specified for WebProfile") + } + + url := fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payment-experience/web-profiles/", wp.ID) + + req, err := c.NewRequest(http.MethodPut, url, wp) + + if err != nil { + return err + } + + err = c.SendWithAuth(req, nil) + if err != nil { + return err + } + + return nil +} + +// DeleteWebProfile deletes a web experience profile from Paypal with given id +// +// Endpoint: DELETE /v1/payment-experience/web-profiles +func (c *Client) DeleteWebProfile(profileID string) error { + + url := fmt.Sprintf("%s%s%s", c.APIBase, "/v1/payment-experience/web-profiles/", profileID) + + req, err := c.NewRequest(http.MethodPut, url, nil) + + if err != nil { + return err + } + + err = c.SendWithAuth(req, nil) + if err != nil { + return err + } + + return nil +} diff --git a/webprofile_test.go b/webprofile_test.go new file mode 100644 index 0000000..ac795b2 --- /dev/null +++ b/webprofile_test.go @@ -0,0 +1,399 @@ +package paypalsdk + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" +) + +type webprofileTestServer struct { + t *testing.T +} + +// ServeHTTP implements http.Handler +func (ts *webprofileTestServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ts.t.Log(r.RequestURI) + if r.RequestURI == "/v1/payment-experience/web-profiles" { + if r.Method == http.MethodPost { + ts.create(w, r) + } + if r.Method == http.MethodGet { + ts.list(w, r) + } + } + if r.RequestURI == "/v1/payment-experience/web-profiles/XP-CP6S-W9DY-96H8-MVN2" { + if r.Method == http.MethodGet { + ts.getvalid(w, r) + } + if r.Method == http.MethodPut { + ts.updatevalid(w, r) + } + if r.Method == http.MethodDelete { + ts.deletevalid(w, r) + } + } + if r.RequestURI == "/v1/payment-experience/web-profiles/foobar" { + if r.Method == http.MethodGet { + ts.getinvalid(w, r) + } + if r.Method == http.MethodPut { + ts.updateinvalid(w, r) + } + if r.Method == http.MethodDelete { + ts.deleteinvalid(w, r) + } + } +} + +func (ts *webprofileTestServer) create(w http.ResponseWriter, r *http.Request) { + var data map[string]interface{} + + body, err := ioutil.ReadAll(r.Body) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + err = json.Unmarshal(body, &data) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + var raw map[string]string + + w.Header().Set("Content-Type", "application/json") + + if name, ok := data["name"]; !ok || name == "" { + raw = map[string]string{ + "name": "VALIDATION_ERROR", + "message": "should have name", + } + w.WriteHeader(http.StatusBadRequest) + } else { + raw = map[string]string{ + "id": "XP-CP6S-W9DY-96H8-MVN2", + } + w.WriteHeader(http.StatusCreated) + } + + res, _ := json.Marshal(raw) + w.Write(res) +} + +func (ts *webprofileTestServer) updatevalid(w http.ResponseWriter, r *http.Request) { + var data map[string]interface{} + + body, err := ioutil.ReadAll(r.Body) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + err = json.Unmarshal(body, &data) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + if ID, ok := data["id"]; !ok || ID != "XP-CP6S-W9DY-96H8-MVN2" { + raw := map[string]string{ + "name": "INVALID_RESOURCE_ID", + "message": "id invalid", + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + res, _ := json.Marshal(raw) + w.Write(res) + return + } + + if name, ok := data["name"]; !ok || name == "" { + raw := map[string]string{ + "name": "VALIDATION_ERROR", + "message": "should have name", + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + res, _ := json.Marshal(raw) + w.Write(res) + return + } + + w.WriteHeader(http.StatusNoContent) + +} + +func (ts *webprofileTestServer) updateinvalid(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + + raw := map[string]interface{}{ + "name": "INVALID_RESOURCE_ID", + "message": "foobar not found", + } + + res, _ := json.Marshal(raw) + w.Write(res) +} + +func (ts *webprofileTestServer) getvalid(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + raw := map[string]interface{}{ + "id": "XP-CP6S-W9DY-96H8-MVN2", + "name": "YeowZa! T-Shirt Shop", + "presentation": map[string]interface{}{ + "brand_name": "YeowZa! Paypal", + "logo_image": "http://www.yeowza.com", + "locale_code": "US", + }, + + "input_fields": map[string]interface{}{ + "allow_note": true, + "no_shipping": 0, + "address_override": 1, + }, + + "flow_config": map[string]interface{}{ + "landing_page_type": "Billing", + "bank_txn_pending_url": "http://www.yeowza.com", + }, + } + + res, _ := json.Marshal(raw) + w.Write(res) +} + +func (ts *webprofileTestServer) getinvalid(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + + raw := map[string]interface{}{ + "name": "INVALID_RESOURCE_ID", + "message": "foobar not found", + } + + res, _ := json.Marshal(raw) + w.Write(res) +} + +func (ts *webprofileTestServer) list(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + raw := []interface{}{ + map[string]interface{}{ + "id": "XP-CP6S-W9DY-96H8-MVN2", + "name": "YeowZa! T-Shirt Shop", + }, + map[string]interface{}{ + "id": "XP-96H8-MVN2-CP6S-W9DY", + "name": "Shop T-Shirt YeowZa! ", + }, + } + + res, _ := json.Marshal(raw) + w.Write(res) +} + +func (ts *webprofileTestServer) deleteinvalid(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + + raw := map[string]interface{}{ + "name": "INVALID_RESOURCE_ID", + "message": "foobar not found", + } + + res, _ := json.Marshal(raw) + w.Write(res) +} + +func (ts *webprofileTestServer) deletevalid(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNoContent) +} + +func TestCreateWebProfile_valid(t *testing.T) { + ts := httptest.NewServer(&webprofileTestServer{t: t}) + defer ts.Close() + + c, _ := NewClient("foo", "bar", ts.URL) + + wp := WebProfile{ + Name: "YeowZa! T-Shirt Shop", + Presentation: Presentation{ + BrandName: "YeowZa! Paypal", + LogoImage: "http://www.yeowza.com", + LocaleCode: "US", + }, + + InputFields: InputFields{ + AllowNote: true, + NoShipping: NoShippingDisplay, + AddressOverride: AddrOverrideFromCall, + }, + + FlowConfig: FlowConfig{ + LandingPageType: LandingPageTypeBilling, + BankTXNPendingURL: "http://www.yeowza.com", + }, + } + + res, err := c.CreateWebProfile(wp) + + if err != nil { + t.Fatal(err) + } + + if res.ID != "XP-CP6S-W9DY-96H8-MVN2" { + t.Fatalf("expecting response to have ID = `XP-CP6S-W9DY-96H8-MVN2` got `%s`", res.ID) + } +} + +func TestCreateWebProfile_invalid(t *testing.T) { + ts := httptest.NewServer(&webprofileTestServer{t: t}) + defer ts.Close() + + c, _ := NewClient("foo", "bar", ts.URL) + + wp := WebProfile{} + + _, err := c.CreateWebProfile(wp) + + if err == nil { + t.Fatalf("expecting an error got nil") + } +} + +func TestGetWebProfile_valid(t *testing.T) { + ts := httptest.NewServer(&webprofileTestServer{t: t}) + defer ts.Close() + + c, _ := NewClient("foo", "bar", ts.URL) + + res, err := c.GetWebProfile("XP-CP6S-W9DY-96H8-MVN2") + + if err != nil { + t.Fatal(err) + } + + if res.ID != "XP-CP6S-W9DY-96H8-MVN2" { + t.Fatalf("expecting res.ID to have value = `XP-CP6S-W9DY-96H8-MVN2` but got `%s`", res.ID) + } + + if res.Name != "YeowZa! T-Shirt Shop" { + t.Fatalf("expecting res.Name to have value = `YeowZa! T-Shirt Shop` but got `%s`", res.Name) + } +} + +func TestGetWebProfile_invalid(t *testing.T) { + ts := httptest.NewServer(&webprofileTestServer{t: t}) + defer ts.Close() + + c, _ := NewClient("foo", "bar", ts.URL) + + _, err := c.GetWebProfile("foobar") + + if err == nil { + t.Fatalf("expecting an error got nil") + } +} + +func TestGetWebProfiles(t *testing.T) { + ts := httptest.NewServer(&webprofileTestServer{t: t}) + defer ts.Close() + + c, _ := NewClient("foo", "bar", ts.URL) + + res, err := c.GetWebProfiles() + + if err != nil { + t.Fatal(err) + } + + if len(res) != 2 { + t.Fatalf("expecting two results got %d", len(res)) + } +} + +func TestSetWebProfile_valid(t *testing.T) { + ts := httptest.NewServer(&webprofileTestServer{t: t}) + defer ts.Close() + + c, _ := NewClient("foo", "bar", ts.URL) + + wp := WebProfile{ + ID: "XP-CP6S-W9DY-96H8-MVN2", + Name: "Shop T-Shirt YeowZa!", + } + + err := c.SetWebProfile(wp) + + if err != nil { + t.Fatal(err) + } + +} + +func TestSetWebProfile_invalid(t *testing.T) { + ts := httptest.NewServer(&webprofileTestServer{t: t}) + defer ts.Close() + + c, _ := NewClient("foo", "bar", ts.URL) + + wp := WebProfile{ + ID: "foobar", + } + + err := c.SetWebProfile(wp) + + if err == nil { + t.Fatal(err) + } + + wp = WebProfile{} + + err = c.SetWebProfile(wp) + + if err == nil { + t.Fatal(err) + } +} + +func TestDeleteWebProfile_valid(t *testing.T) { + ts := httptest.NewServer(&webprofileTestServer{t: t}) + defer ts.Close() + + c, _ := NewClient("foo", "bar", ts.URL) + + wp := WebProfile{ + ID: "XP-CP6S-W9DY-96H8-MVN2", + Name: "Shop T-Shirt YeowZa!", + } + + err := c.SetWebProfile(wp) + + if err != nil { + t.Fatal(err) + } + +} + +func TestDeleteWebProfile_invalid(t *testing.T) { + ts := httptest.NewServer(&webprofileTestServer{t: t}) + defer ts.Close() + + c, _ := NewClient("foo", "bar", ts.URL) + + err := c.DeleteWebProfile("foobar") + + if err == nil { + t.Fatal(err) + } + +}