From 01cbb3be4a49a7b615589e2c2460bda94dd992df Mon Sep 17 00:00:00 2001 From: ____ Date: Mon, 10 Jul 2023 19:12:40 -0700 Subject: [PATCH 01/10] persona api wrapper --- pkg/internal/persona/client.go | 67 ++++++++++++++ pkg/internal/persona/inquiries.go | 40 +++++++++ pkg/internal/persona/persona_test.go | 125 +++++++++++++++++++++++++++ pkg/internal/persona/templates.go | 31 +++++++ pkg/internal/persona/verification.go | 46 ++++++++++ 5 files changed, 309 insertions(+) create mode 100644 pkg/internal/persona/client.go create mode 100644 pkg/internal/persona/inquiries.go create mode 100644 pkg/internal/persona/persona_test.go create mode 100644 pkg/internal/persona/templates.go create mode 100644 pkg/internal/persona/verification.go diff --git a/pkg/internal/persona/client.go b/pkg/internal/persona/client.go new file mode 100644 index 00000000..dce80f7d --- /dev/null +++ b/pkg/internal/persona/client.go @@ -0,0 +1,67 @@ +package persona + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" +) + +type PersonaClient struct { + BaseURL string + APIKey string + Client *http.Client +} + +func New(apiKey string) *PersonaClient { + return &PersonaClient{ + APIKey: apiKey, + BaseURL: "https://withpersona.com/api/", + Client: &http.Client{}, + } +} + +func NewPersonaClient(baseURL, apiKey string) *PersonaClient { + return &PersonaClient{ + BaseURL: baseURL, + APIKey: apiKey, + Client: &http.Client{}, + } +} + +func (c *PersonaClient) doRequest(method, url string, payload, result interface{}) error { + var body io.Reader + if payload != nil { + jsonPayload, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("failed to marshal payload: %w", err) + } + body = bytes.NewBuffer(jsonPayload) + } + + req, err := http.NewRequest(method, c.BaseURL+url, body) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.APIKey)) + req.Header.Set("Content-Type", "application/json") + + resp, err := c.Client.Do(req) + if err != nil { + return fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("request failed with status code: %d", resp.StatusCode) + } + + if result != nil { + if err := json.NewDecoder(resp.Body).Decode(result); err != nil { + return fmt.Errorf("failed to decode response: %w", err) + } + } + + return nil +} diff --git a/pkg/internal/persona/inquiries.go b/pkg/internal/persona/inquiries.go new file mode 100644 index 00000000..d7d1426d --- /dev/null +++ b/pkg/internal/persona/inquiries.go @@ -0,0 +1,40 @@ +package persona + +import ( + "fmt" + "net/http" +) + +type Inquiry struct { + Id string `json:"id"` + Status string `json:"status"` + CreatedAt string `json:"created_at"` + LastUpdatedAt string `json:"last_updated_at"` + CompletedSteps []struct { + Type string `json:"type"` + Status string `json:"status"` + } `json:"completed_steps"` +} + +type InquiryPayload struct { + AccountID string `json:"account_id"` + Template string `json:"template"` +} + +func (c *PersonaClient) CreateInquiry(payload InquiryPayload) (*Inquiry, error) { + inquiry := &Inquiry{} + err := c.doRequest(http.MethodPost, "/v1/inquiries", payload, inquiry) + if err != nil { + return nil, fmt.Errorf("failed to create inquiry: %w", err) + } + return inquiry, nil +} + +func (c *PersonaClient) GetInquiry(id string) (*Inquiry, error) { + inquiry := &Inquiry{} + err := c.doRequest(http.MethodGet, "/v1/inquiries/"+id, nil, inquiry) + if err != nil { + return nil, fmt.Errorf("failed to get inquiry: %w", err) + } + return inquiry, nil +} diff --git a/pkg/internal/persona/persona_test.go b/pkg/internal/persona/persona_test.go new file mode 100644 index 00000000..e5afa8c5 --- /dev/null +++ b/pkg/internal/persona/persona_test.go @@ -0,0 +1,125 @@ +package persona + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func testServer(handler func(w http.ResponseWriter, r *http.Request)) *httptest.Server { + return httptest.NewServer(http.HandlerFunc(handler)) +} + +func TestNew(t *testing.T) { + c := New("test-key") + if c == nil { + t.Error("Expected persona client to be created") + } +} + +func TestDoRequest(t *testing.T) { + testServer := testServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + res.Write([]byte(`OK`)) + })) + defer func() { testServer.Close() }() + + c := New("test-key") + c.BaseURL = testServer.URL + + err := c.doRequest("GET", "/", nil, nil) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } +} + +func TestGetVerifications(t *testing.T) { + testServer := testServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + res.Write([]byte(`[{"id":"test-verification"}]`)) + })) + defer func() { testServer.Close() }() + + c := New("test-key") + c.BaseURL = testServer.URL + + v, err := c.GetVerifications() + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + if len(v) != 1 { + t.Errorf("Expected one verification, got %v", len(v)) + } +} + +func TestGetTemplates(t *testing.T) { + testServer := testServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + res.Write([]byte(`[{"id":"test-template"}]`)) + })) + defer func() { testServer.Close() }() + + c := New("test-key") + c.BaseURL = testServer.URL + + te, err := c.GetTemplates() + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + if len(te) != 1 { + t.Errorf("Expected one template, got %v", len(te)) + } +} + +func TestCreateInquiry(t *testing.T) { + server := testServer(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/v1/inquiries", r.URL.String()) + assert.Equal(t, http.MethodPost, r.Method) + + var payload InquiryPayload + err := json.NewDecoder(r.Body).Decode(&payload) + assert.NoError(t, err) + assert.Equal(t, "test-account", payload.AccountID) + assert.Equal(t, "test-template", payload.Template) + + inquiry := &Inquiry{ + Id: "test-id", + Status: "completed", + } + + json.NewEncoder(w).Encode(inquiry) + }) + defer server.Close() + + client := NewPersonaClient(server.URL, "test-key") + inquiry, err := client.CreateInquiry(InquiryPayload{ + AccountID: "test-account", + Template: "test-template", + }) + assert.NoError(t, err) + assert.Equal(t, "test-id", inquiry.Id) + assert.Equal(t, "completed", inquiry.Status) +} + +func TestGetInquiry(t *testing.T) { + server := testServer(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/v1/inquiries/test-id", r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + + inquiry := &Inquiry{ + Id: "test-id", + Status: "completed", + } + + json.NewEncoder(w).Encode(inquiry) + }) + defer server.Close() + + client := NewPersonaClient(server.URL, "test-key") + inquiry, err := client.GetInquiry("test-id") + assert.NoError(t, err) + assert.Equal(t, "test-id", inquiry.Id) + assert.Equal(t, "completed", inquiry.Status) +} diff --git a/pkg/internal/persona/templates.go b/pkg/internal/persona/templates.go new file mode 100644 index 00000000..a73f4a39 --- /dev/null +++ b/pkg/internal/persona/templates.go @@ -0,0 +1,31 @@ +package persona + +import ( + "fmt" + "net/http" +) + +type Template struct { + Id string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + CreatedAt string `json:"created_at"` +} + +func (c *PersonaClient) GetTemplates() ([]Template, error) { + var templates []Template + err := c.doRequest(http.MethodGet, "/v1/templates", nil, &templates) + if err != nil { + return nil, fmt.Errorf("failed to get templates: %w", err) + } + return templates, nil +} + +func (c *PersonaClient) GetTemplate(id string) (*Template, error) { + template := &Template{} + err := c.doRequest(http.MethodGet, fmt.Sprintf("/v1/templates/%s", id), nil, template) + if err != nil { + return nil, fmt.Errorf("failed to get template: %w", err) + } + return template, nil +} diff --git a/pkg/internal/persona/verification.go b/pkg/internal/persona/verification.go new file mode 100644 index 00000000..9bc4d5cf --- /dev/null +++ b/pkg/internal/persona/verification.go @@ -0,0 +1,46 @@ +package persona + +import ( + "fmt" + "net/http" +) + +type IdentityVerification struct { + Id string `json:"id"` + Status string `json:"status"` + CreatedAt string `json:"created_at"` + LastUpdatedAt string `json:"last_updated_at"` + CompletedSteps []struct { + Type string `json:"type"` + Status string `json:"status"` + } `json:"completed_steps"` +} + +type IdentityVerificationPayload struct { + AccountId string `json:"account_id"` + Template string `json:"template"` +} + +type Verification struct { + Id string `json:"id"` + Status string `json:"status"` +} + +func (c *PersonaClient) VerifyIdentity(payload IdentityVerificationPayload) (*IdentityVerification, error) { + verification := &IdentityVerification{} + err := c.doRequest(http.MethodPost, "/v1/verifications", payload, verification) + if err != nil { + return nil, fmt.Errorf("failed to verify identity: %w", err) + } + return verification, nil +} + +func (c *PersonaClient) GetVerifications() ([]Verification, error) { + var verifications []Verification + err := c.doRequest(http.MethodGet, "/v1/verifications", nil, &verifications) + if err != nil { + return nil, fmt.Errorf("failed to do request: %w", err) + } + + return verifications, nil +} From fe8bc95d759d394e14acd38c01115370fd64e09a Mon Sep 17 00:00:00 2001 From: ____ Date: Fri, 21 Jul 2023 15:37:08 -0700 Subject: [PATCH 02/10] types --- pkg/internal/persona/account.go | 41 +++ pkg/internal/persona/client.go | 2 + pkg/internal/persona/inquiries.go | 48 ++-- .../persona/persona_integration_test.go | 4 + pkg/internal/persona/persona_test.go | 21 +- pkg/internal/persona/types.go | 243 ++++++++++++++++++ pkg/internal/persona/verification.go | 61 +++-- 7 files changed, 359 insertions(+), 61 deletions(-) create mode 100644 pkg/internal/persona/account.go create mode 100644 pkg/internal/persona/persona_integration_test.go create mode 100644 pkg/internal/persona/types.go diff --git a/pkg/internal/persona/account.go b/pkg/internal/persona/account.go new file mode 100644 index 00000000..7d8c5441 --- /dev/null +++ b/pkg/internal/persona/account.go @@ -0,0 +1,41 @@ +package persona + +import ( + "net/http" + + "github.com/String-xyz/go-lib/v2/common" +) + +/* + +The account represents a verified individual and contains one or more inquiries. +The primary use of the account endpoints is to fetch previously submitted information for an individual. + +*/ + +func (c *PersonaClient) CreateAccount(payload InquiryPayload) (*Inquiry, error) { + inquiry := &Inquiry{} + err := c.doRequest(http.MethodPost, "/v1/accounts", payload, inquiry) + if err != nil { + return nil, common.StringError(err, "failed to create an account") + } + return inquiry, nil +} + +func (c *PersonaClient) GetAccountById(id string) (*AccountResponse, error) { + account := &AccountResponse{} + err := c.doRequest(http.MethodGet, "/v1/accounts/"+id, nil, account) + if err != nil { + return nil, common.StringError(err, "failed to get account") + } + return account, nil +} + +func (c *PersonaClient) ListAccounts() (*ListAccountResponse, error) { + accounts := &ListAccountResponse{} + err := c.doRequest(http.MethodGet, "/v1/accounts", nil, accounts) + if err != nil { + return nil, common.StringError(err, "failed to list accounts") + } + return accounts, nil +} diff --git a/pkg/internal/persona/client.go b/pkg/internal/persona/client.go index dce80f7d..28934414 100644 --- a/pkg/internal/persona/client.go +++ b/pkg/internal/persona/client.go @@ -44,8 +44,10 @@ func (c *PersonaClient) doRequest(method, url string, payload, result interface{ if err != nil { return fmt.Errorf("failed to create request: %w", err) } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.APIKey)) req.Header.Set("Content-Type", "application/json") + req.Header.Set("Key-Inflection", "camel") resp, err := c.Client.Do(req) if err != nil { diff --git a/pkg/internal/persona/inquiries.go b/pkg/internal/persona/inquiries.go index d7d1426d..1ee494f2 100644 --- a/pkg/internal/persona/inquiries.go +++ b/pkg/internal/persona/inquiries.go @@ -1,40 +1,52 @@ package persona import ( - "fmt" "net/http" + + "github.com/String-xyz/go-lib/v2/common" ) -type Inquiry struct { - Id string `json:"id"` - Status string `json:"status"` - CreatedAt string `json:"created_at"` - LastUpdatedAt string `json:"last_updated_at"` - CompletedSteps []struct { - Type string `json:"type"` - Status string `json:"status"` - } `json:"completed_steps"` -} +/* +The inquiry represents a single instance of an individual attempting to verify their identity. +The primary use of the inquiry endpoints is to fetch submitted information from the flow. -type InquiryPayload struct { - AccountID string `json:"account_id"` - Template string `json:"template"` -} +Inquiries are created when the individual begins to verify their identity. +Check for the following statuses to determine whether the individual has finished the flow. + +* Created - The individual started the inquiry. +* Pending - The individual submitted a verification within the inquiry. +* Completed - The individual passed all required verifications within the inquiry. + +Approved/Declined (Optional) +These are optional statuses applied by you to execute custom decisioning logic. +* Expired - The individual did not complete the inquiry within 24 hours. +* Failed - The individual exceeded the allowed number of verification attempts on the inquiry and cannot continue. + +*/ func (c *PersonaClient) CreateInquiry(payload InquiryPayload) (*Inquiry, error) { inquiry := &Inquiry{} err := c.doRequest(http.MethodPost, "/v1/inquiries", payload, inquiry) if err != nil { - return nil, fmt.Errorf("failed to create inquiry: %w", err) + return nil, common.StringError(err, "failed to create inquiry") } return inquiry, nil } -func (c *PersonaClient) GetInquiry(id string) (*Inquiry, error) { +func (c *PersonaClient) GetInquiryById(id string) (*Inquiry, error) { inquiry := &Inquiry{} err := c.doRequest(http.MethodGet, "/v1/inquiries/"+id, nil, inquiry) if err != nil { - return nil, fmt.Errorf("failed to get inquiry: %w", err) + return nil, common.StringError(err, "failed to get inquiry") } return inquiry, nil } + +func (c *PersonaClient) ListInquiriesByAccount(accountId string) (*ListInquiryResponse, error) { + inquiries := &ListInquiryResponse{} + err := c.doRequest(http.MethodGet, "/v1/inquiries?filter[account-id]="+accountId, nil, inquiries) + if err != nil { + return nil, common.StringError(err, "failed to list inquiries") + } + return inquiries, nil +} diff --git a/pkg/internal/persona/persona_integration_test.go b/pkg/internal/persona/persona_integration_test.go new file mode 100644 index 00000000..fd7349d8 --- /dev/null +++ b/pkg/internal/persona/persona_integration_test.go @@ -0,0 +1,4 @@ +//go:build integration +// +build integration + +package persona diff --git a/pkg/internal/persona/persona_test.go b/pkg/internal/persona/persona_test.go index e5afa8c5..dbedcbaf 100644 --- a/pkg/internal/persona/persona_test.go +++ b/pkg/internal/persona/persona_test.go @@ -1,3 +1,6 @@ +//go:build unit +// +build unit + package persona import ( @@ -35,7 +38,7 @@ func TestDoRequest(t *testing.T) { } } -func TestGetVerifications(t *testing.T) { +func TestGetVerificationById(t *testing.T) { testServer := testServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.Write([]byte(`[{"id":"test-verification"}]`)) })) @@ -44,14 +47,10 @@ func TestGetVerifications(t *testing.T) { c := New("test-key") c.BaseURL = testServer.URL - v, err := c.GetVerifications() - if err != nil { - t.Errorf("Expected no error, got %v", err) - } + v, err := c.GetVerificationById("test-verification") - if len(v) != 1 { - t.Errorf("Expected one verification, got %v", len(v)) - } + assert.NoError(t, err) + assert.NotNil(t, v) } func TestGetTemplates(t *testing.T) { @@ -81,7 +80,7 @@ func TestCreateInquiry(t *testing.T) { var payload InquiryPayload err := json.NewDecoder(r.Body).Decode(&payload) assert.NoError(t, err) - assert.Equal(t, "test-account", payload.AccountID) + assert.Equal(t, "test-account", payload.AccountId) assert.Equal(t, "test-template", payload.Template) inquiry := &Inquiry{ @@ -95,7 +94,7 @@ func TestCreateInquiry(t *testing.T) { client := NewPersonaClient(server.URL, "test-key") inquiry, err := client.CreateInquiry(InquiryPayload{ - AccountID: "test-account", + AccountId: "test-account", Template: "test-template", }) assert.NoError(t, err) @@ -118,7 +117,7 @@ func TestGetInquiry(t *testing.T) { defer server.Close() client := NewPersonaClient(server.URL, "test-key") - inquiry, err := client.GetInquiry("test-id") + inquiry, err := client.GetInquiryById("test-id") assert.NoError(t, err) assert.Equal(t, "test-id", inquiry.Id) assert.Equal(t, "completed", inquiry.Status) diff --git a/pkg/internal/persona/types.go b/pkg/internal/persona/types.go new file mode 100644 index 00000000..379375b2 --- /dev/null +++ b/pkg/internal/persona/types.go @@ -0,0 +1,243 @@ +package persona + +import "time" + +type IdValue struct { + Type string `json:"type"` + Id string `json:"id"` +} + +type HashValue struct { + Type string `json:"type"` + Value map[string]Value `json:"value"` +} + +type StringValue struct { + Type string `json:"type"` + Value string `json:"value"` +} + +type ArrayValue struct { + Type string `json:"type"` + Value []HashValue `json:"value"` +} + +type Value struct { + Type string `json:"type"` + Value interface{} `json:"value"` +} + +type Link struct { + Prev *string `json:"prev"` + Next *string `json:"next"` +} + +type PhoneURL struct { + Page *string `json:"page"` + Url *string `json:"url"` + fileName *string `json:"fileName"` + NormalizedUrl *string `json:"normalizedUrl"` + OriginalUrls []string `json:"originalUrls"` + ByteSize int `json:"byteSize"` +} + +type Check struct { + Name string `json:"name"` + Status string `json:"status"` + Reason []interface{} `json:"reason"` + Requirement string `json:"requirement"` + Metadata interface{} `json:"metadata"` +} + +type RelationshipId struct { + Data *IdValue `json:"data"` +} + +type RelationshipIds struct { + Data []IdValue `json:"data"` +} + +type Relationships struct { + Account *RelationshipId `json:"account"` + Inquity *RelationshipId `json:"inquiry"` + Template *RelationshipId `json:"template"` + InquityTemplate *RelationshipId `json:"inquiryTemplate"` + InquityTemplateVersion *RelationshipId `json:"inquiryTemplateVersion"` + VerificationTemplate *RelationshipId `json:"verificationTemplate"` + VerificationTemplateVersion *RelationshipId `json:"verificationTemplateVersion"` + Verifications *RelationshipIds `json:"verifications"` + Sessions *RelationshipIds `json:"sessions"` + Documents *RelationshipIds `json:"documents"` + DocumentFiles *RelationshipIds `json:"documentFiles"` + Selfies *RelationshipIds `json:"selfies"` +} + +type Behavior struct { + RequestSpoofAttempts int `json:"requestSpoofAttempts"` + UserAgentSpoofAttempts int `json:"userAgentSpoofAttempts"` + DistractionEvents int `json:"distractionEvents"` + HesitationBaseline int `json:"hesitationBaseline"` + HesitationCount int `json:"hesitationCount"` + HesitationTime int `json:"hesitationTime"` + ShortcutCopies int `json:"shortcutCopies"` + ShortcutPastes int `json:"shortcutPastes"` + AutofillCancels int `json:"autofillCancels"` + AutofillStarts int `json:"autofillStarts"` + DevtoolsOpen bool `json:"devtoolsOpen"` + CompletionTime float64 `json:"completionTime"` + HesitationPercentage float64 `json:"hesitationPercentage"` + BehaviorThreatLevel string `json:"behaviorThreatLevel"` +} + +type Attribute struct { + Status string `json:"status"` + CreatedAt time.Time `json:"createdAt"` + StartedAt *time.Time `json:"startedAt"` + FailedAt *time.Time `json:"failedAt"` + DecesionedAt *time.Time `json:"decesionedAt"` + MarkForReviewAt *time.Time `json:"markForReviewAt"` + UpdatedAt time.Time `json:"updatedAt"` + RedactedAt *time.Time `json:"redactedAt"` + SubmittedAt *time.Time `json:"submittedAt"` + CompletedAt *time.Time `json:"completedAt"` + ExpiredAt *time.Time `json:"expiredAt"` +} + +type InquiryField struct { + AddressStreet1 StringValue `json:"addressStreet1"` + AddressStreet2 StringValue `json:"addressStreet2"` +} + +type InquiryAttributes struct { + Attribute + ReferenceId *string `json:"referenceId"` + Behaviors Behavior `json:"behaviors"` + Notes *string `json:"notes"` + Tags []interface{} `json:"tags"` + PreviousStepName string `json:"previousStepName"` + NextStepName string `json:"nextStepName"` + Fields InquiryField `json:"fields"` +} + +type VerificationAttributes struct { + Attribute + CountryCode *string `json:"countryCode"` + LeftPhotoUrl *string `json:"leftPhotoUrl"` + RightPhotoUrl *string `json:"rightPhotoUrl"` + CenterPhotoUrl *string `json:"centerPhotoUrl"` + PhotoUrls []PhoneURL `json:"photoUrls"` + Checks []Check `json:"checks"` + CaptureMethod string `json:"captureMethod"` +} + +type IncludeAttributes struct { + VerificationAttributes + SelfiePhoto *string `json:"selfiePhoto"` + SelfiePhotoUrl *string `json:"selfiePhotoUrl"` + FrontPhotoUrl *PhotoURL `json:"frontPhotoUrl"` + BackPhotoUrl *PhoneURL `json:"backPhotoUrl"` + VideoUrl *string `json:"videoUrl"` + IdClass string `json:"idClass"` + CaptureMethod string `json:"captureMethod"` + EntityConfidenceScore int `json:"entityConfidenceScore"` + EntityConfidenceReasons []string `json:"entityConfidenceReasons"` + NameFirst string `json:"nameFirst"` + NameMiddle *string `json:"nameMiddle"` + NameLast string `json:"nameLast"` + NameSuffix *string `json:"nameSuffix"` + Birthdate string `json:"birthdate"` + AddressStreet1 string `json:"addressStreet1"` + AddressStreet2 *string `json:"addressStreet2"` + AddressCity string `json:"addressCity"` + AddressSubdivision string `json:"addressSubdivision"` + AddressPostalCode string `json:"addressPostalCode"` + IssuingAuthority string `json:"issuingAuthority"` + IssuingSubdivision string `json:"issuingSubdivision"` + Nationality *string `json:"nationality"` + DocumentNumber *string `json:"documentNumber"` + VisaStatus *string `json:"visaStatus"` + IssueDate string `json:"issueDate"` + ExpirationDate string `json:"expirationDate"` + Designations *string `json:"designations"` + Birthplace *string `json:"birthplace"` + Endorsements *string `json:"endorsements"` + Height *string `json:"height"` + Sex string `json:"sex"` + Restrictions *string `json:"restrictions"` + VehicleClass *string `json:"vehicleClass"` + IdentificationNumber string `json:"identificationNumber"` +} + +type AccountField struct { + Name HashValue `json:"name"` + Address HashValue `json:"address"` + IdentificationNumbers ArrayValue `json:"identificationNumbers"` + Birthdate Value `json:"birthdate"` + PhoneNumber Value `json:"phoneNumber"` + EmailAddress Value `json:"emailAddress"` + SelfiePhoto Value `json:"selfiePhoto"` +} + +type Include struct { + Id string `json:"id"` + Type string `json:"type"` + Atrributes IncludeAttributes `json:"attributes"` + Relationships Relationships `json:"relationships"` +} + +type Inquiry struct { + Id string `json:"id"` + Status string `json:"status"` + CreatedAt string `json:"createdAt"` + LastUpdatedAt string `json:"lastUpdatedAt"` + CompletedSteps CompletedSteps `json:"completedSteps"` +} + +type InquiryPayload struct { + AccountId string `json:"accountId"` + Template string `json:"template"` +} + +type CompletedSteps struct { + Type string `json:"type"` + Status string `json:"status"` +} + +type IdentityVerification struct { + Id string `json:"id"` + Status string `json:"status"` + CreatedAt string `json:"createdAt"` + LastUpdatedAt string `json:"lastUpdatedAt"` + CompletedSteps CompletedSteps `json:"completedSteps"` +} + +type IdentityVerificationPayload struct { + AccountId string `json:"accountId"` + Template string `json:"template"` +} + +type Verification struct { + Id string `json:"id"` + Attributes VerificationAttributes `json:"attributes"` + Relationships Relationship `json:"relationships"` +} + +type Account struct { + Type string `json:"type"` + Id string `json:"id"` + Attributes Attribute `json:"attributes"` +} + +type AccountResponse struct { + Data Account `json:"data"` +} + +type ListAccountResponse struct { + Data []Account `json:"data"` + Links Link `json:"links"` +} + +type ListInquiryResponse struct { + Data []Inquiry `json:"data"` + Links Link `json:"links"` +} diff --git a/pkg/internal/persona/verification.go b/pkg/internal/persona/verification.go index 9bc4d5cf..d3a2864f 100644 --- a/pkg/internal/persona/verification.go +++ b/pkg/internal/persona/verification.go @@ -1,46 +1,43 @@ package persona import ( - "fmt" "net/http" + + "github.com/String-xyz/go-lib/v2/common" ) -type IdentityVerification struct { - Id string `json:"id"` - Status string `json:"status"` - CreatedAt string `json:"created_at"` - LastUpdatedAt string `json:"last_updated_at"` - CompletedSteps []struct { - Type string `json:"type"` - Status string `json:"status"` - } `json:"completed_steps"` -} +/* -type IdentityVerificationPayload struct { - AccountId string `json:"account_id"` - Template string `json:"template"` -} +To verify a set of inputs from an individual, a Verification object is created. +A Verification enables an Organization to answer “Is this person who they claim to be?” with a focus on verifying digital transactions. +The verification process is accomplished through the generation and processing of Checks against the information provided by the individual. -type Verification struct { - Id string `json:"id"` - Status string `json:"status"` -} +The collective process of mixing and matching verifications to achieve sufficient assurance that +an individual is indeed who they claim to be is often called identity proofing. +The goal of identity proofing is often tying digital identities to physical identities. -func (c *PersonaClient) VerifyIdentity(payload IdentityVerificationPayload) (*IdentityVerification, error) { - verification := &IdentityVerification{} - err := c.doRequest(http.MethodPost, "/v1/verifications", payload, verification) - if err != nil { - return nil, fmt.Errorf("failed to verify identity: %w", err) - } - return verification, nil -} +An Inquiry contains one or more verifications. The attributes available for any given verification depends on its type. +Each inquiry’s relationships field lists the IDs of all associated verifications. +To authenticate when fetching photo URLs, pass the same Authorization header. + +Verifications change statuses as the individual progresses through the flow. +Check for the following statuses to monitor progress and find completed results. -func (c *PersonaClient) GetVerifications() ([]Verification, error) { - var verifications []Verification - err := c.doRequest(http.MethodGet, "/v1/verifications", nil, &verifications) +* Initiated - Verification has started, claimed information can now sent and saved to the server for verification +* Confirmed - Verification has been confirmed. This is a status specific to PhoneNumber verifications where they have verified a confirmation code that was entered. +* Submitted - Verification has been submitted, the claimed information is frozen and the server will process the verification +* Passed - Verification has passed. The required checks have passed and the information is verified +* Requires Retry - Verification requires a resubmission. The checks could not be fully processed due to issues with the submitted information +* Failed - Verification has failed. Some or all of the required checks have failed and verification has failed + +*/ + +func (c *PersonaClient) GetVerificationById(id string) (*Verification, error) { + verification := &Verification{} + err := c.doRequest(http.MethodGet, "/v1/verifications/"+id, nil, verification) if err != nil { - return nil, fmt.Errorf("failed to do request: %w", err) + return nil, common.StringError(err, "failed to get verification by id") } - return verifications, nil + return verification, nil } From 78d0ffeb6fdbd2e2693f64179538e6b5417caa2e Mon Sep 17 00:00:00 2001 From: ____ Date: Sat, 22 Jul 2023 15:19:22 -0700 Subject: [PATCH 03/10] accounts and inquiries --- pkg/internal/persona/account.go | 35 +++++++-- pkg/internal/persona/inquiries.go | 34 ++++++++- pkg/internal/persona/types.go | 114 ++++++++++++++++++------------ 3 files changed, 127 insertions(+), 56 deletions(-) diff --git a/pkg/internal/persona/account.go b/pkg/internal/persona/account.go index 7d8c5441..daf4b313 100644 --- a/pkg/internal/persona/account.go +++ b/pkg/internal/persona/account.go @@ -7,19 +7,40 @@ import ( ) /* - The account represents a verified individual and contains one or more inquiries. The primary use of the account endpoints is to fetch previously submitted information for an individual. - */ -func (c *PersonaClient) CreateAccount(payload InquiryPayload) (*Inquiry, error) { - inquiry := &Inquiry{} - err := c.doRequest(http.MethodPost, "/v1/accounts", payload, inquiry) +type Account struct { + Id string `json:"id"` + Type string `json:"type"` + Attributes AccountAttributes `json:"attributes"` +} + +type AccountCreate struct { + Attributes CommonFields `json:"attributes"` +} + +type AccountCreateRequest struct { + Data AccountCreate `json:"data"` +} + +type AccountResponse struct { + Data Account `json:"data"` +} + +type ListAccountResponse struct { + Data []Account `json:"data"` + Links Link `json:"links"` +} + +func (c *PersonaClient) CreateAccount(request AccountCreateRequest) (*AccountResponse, error) { + account := &AccountResponse{} + err := c.doRequest(http.MethodPost, "/v1/accounts", request, account) if err != nil { - return nil, common.StringError(err, "failed to create an account") + return nil, common.StringError(err, "failed to create account") } - return inquiry, nil + return account, nil } func (c *PersonaClient) GetAccountById(id string) (*AccountResponse, error) { diff --git a/pkg/internal/persona/inquiries.go b/pkg/internal/persona/inquiries.go index 1ee494f2..6475b305 100644 --- a/pkg/internal/persona/inquiries.go +++ b/pkg/internal/persona/inquiries.go @@ -21,12 +21,40 @@ Approved/Declined (Optional) These are optional statuses applied by you to execute custom decisioning logic. * Expired - The individual did not complete the inquiry within 24 hours. * Failed - The individual exceeded the allowed number of verification attempts on the inquiry and cannot continue. - */ -func (c *PersonaClient) CreateInquiry(payload InquiryPayload) (*Inquiry, error) { +type InquiryCreate struct { + Attributes InquiryCreationAttributes `json:"attributes"` +} + +type InquiryCreateRequest struct { + Data InquiryCreate `json:"data"` +} + +type Inquiry struct { + Id string `json:"id"` + Type string `json:"type"` + Attributes InquiryAttributes `json:"attributes"` + Relationships Relationships `json:"relationships"` +} + +type InquiryResponse struct { + Data Inquiry `json:"data"` + Included []Included `json:"included"` +} + +type InquiryListItem struct { + Data Inquiry `json:"data"` +} + +type ListInquiryResponse struct { + Data []InquiryListItem `json:"data"` + Links Link `json:"links"` +} + +func (c *PersonaClient) CreateInquiry(request InquiryCreateRequest) (*Inquiry, error) { inquiry := &Inquiry{} - err := c.doRequest(http.MethodPost, "/v1/inquiries", payload, inquiry) + err := c.doRequest(http.MethodPost, "/v1/inquiries", request, inquiry) if err != nil { return nil, common.StringError(err, "failed to create inquiry") } diff --git a/pkg/internal/persona/types.go b/pkg/internal/persona/types.go index 379375b2..2d0bd29f 100644 --- a/pkg/internal/persona/types.go +++ b/pkg/internal/persona/types.go @@ -32,7 +32,7 @@ type Link struct { Next *string `json:"next"` } -type PhoneURL struct { +type PhotoURL struct { Page *string `json:"page"` Url *string `json:"url"` fileName *string `json:"fileName"` @@ -89,6 +89,21 @@ type Behavior struct { BehaviorThreatLevel string `json:"behaviorThreatLevel"` } +type AccountField struct { + Name HashValue `json:"name"` + Address HashValue `json:"address"` + IdentificationNumbers ArrayValue `json:"identificationNumbers"` + Birthdate Value `json:"birthdate"` + PhoneNumber Value `json:"phoneNumber"` + EmailAddress Value `json:"emailAddress"` + SelfiePhoto Value `json:"selfiePhoto"` +} + +type InquiryField struct { + AddressStreet1 StringValue `json:"addressStreet1"` + AddressStreet2 StringValue `json:"addressStreet2"` +} + type Attribute struct { Status string `json:"status"` CreatedAt time.Time `json:"createdAt"` @@ -103,9 +118,54 @@ type Attribute struct { ExpiredAt *time.Time `json:"expiredAt"` } -type InquiryField struct { - AddressStreet1 StringValue `json:"addressStreet1"` - AddressStreet2 StringValue `json:"addressStreet2"` +type AccountAttributes struct { + Attribute + Fields AccountField `json:"fields"` +} + +type CommonFields struct { + // City of residence address. Not all international addresses use this attribute. + AddresCity string `json:"address-city,omitempty"` + // Street name of residence address. + AddressStreet1 string `json:"address-street-1,omitempty"` + // Extension of residence address, usually apartment or suite number. + AddressStreet2 string `json:"address-street-2,omitempty"` + // State or subdivision of residence address. In the US, + // this should be the unabbreviated name. Not all international addresses use this attribute. + AddressSubdivision string `json:"address-subdivision,omitempty"` + // Postal code of residence address. Not all international addresses use this attribute. + AddressPostalCode string `json:"address-postal-code,omitempty"` + // Birthdate, must be in the format "YYYY-MM-DD". + Birthdate string `json:"birthdate,omitempty"` + // ISO 3166-1 alpha 2 country code of the government ID to be verified. This is generally their country of residence as well. + CountryCode string `json:"country-code,omitempty"` + + EmailAddress string `json:"email-address,omitempty"` + // Given or first name. + NameFirst string `json:"name-first,omitempty"` + // Family or last name. + NameLast string `json:"name-last,omitempty"` + + NameMiddle string `json:"name-middle,omitempty"` + + PhoneNumber string `json:"phone-number,omitempty"` + + SocialSecurityNumber string `json:"social-security-number,omitempty"` +} + +type InquiryCreationAttributes struct { + AccountId string `json:"account-id"` + CountryCode string `json:"country-code"` + InquityTemplateId string `json:"inquiry-template-id"` + InquityTemplateVersionId string `json:"inquiry-template-version-id"` + // Template ID for flow requirements (use this field if your template ID starts with tmpl_). + // You must pass in either template-id OR inquiry-template-id OR inquiry-template-version-id + TemplateId string `json:"template-id"` + TemplateVersionId string `json:"template-version-id"` + // for styling + ThemeId string `json:"theme-id"` + + Fields CommonFields `json:"fields"` } type InquiryAttributes struct { @@ -125,7 +185,7 @@ type VerificationAttributes struct { LeftPhotoUrl *string `json:"leftPhotoUrl"` RightPhotoUrl *string `json:"rightPhotoUrl"` CenterPhotoUrl *string `json:"centerPhotoUrl"` - PhotoUrls []PhoneURL `json:"photoUrls"` + PhotoUrls []PhotoURL `json:"photoUrls"` Checks []Check `json:"checks"` CaptureMethod string `json:"captureMethod"` } @@ -135,7 +195,7 @@ type IncludeAttributes struct { SelfiePhoto *string `json:"selfiePhoto"` SelfiePhotoUrl *string `json:"selfiePhotoUrl"` FrontPhotoUrl *PhotoURL `json:"frontPhotoUrl"` - BackPhotoUrl *PhoneURL `json:"backPhotoUrl"` + BackPhotoUrl *PhotoURL `json:"backPhotoUrl"` VideoUrl *string `json:"videoUrl"` IdClass string `json:"idClass"` CaptureMethod string `json:"captureMethod"` @@ -168,31 +228,13 @@ type IncludeAttributes struct { IdentificationNumber string `json:"identificationNumber"` } -type AccountField struct { - Name HashValue `json:"name"` - Address HashValue `json:"address"` - IdentificationNumbers ArrayValue `json:"identificationNumbers"` - Birthdate Value `json:"birthdate"` - PhoneNumber Value `json:"phoneNumber"` - EmailAddress Value `json:"emailAddress"` - SelfiePhoto Value `json:"selfiePhoto"` -} - -type Include struct { +type Included struct { Id string `json:"id"` Type string `json:"type"` Atrributes IncludeAttributes `json:"attributes"` Relationships Relationships `json:"relationships"` } -type Inquiry struct { - Id string `json:"id"` - Status string `json:"status"` - CreatedAt string `json:"createdAt"` - LastUpdatedAt string `json:"lastUpdatedAt"` - CompletedSteps CompletedSteps `json:"completedSteps"` -} - type InquiryPayload struct { AccountId string `json:"accountId"` Template string `json:"template"` @@ -219,25 +261,5 @@ type IdentityVerificationPayload struct { type Verification struct { Id string `json:"id"` Attributes VerificationAttributes `json:"attributes"` - Relationships Relationship `json:"relationships"` -} - -type Account struct { - Type string `json:"type"` - Id string `json:"id"` - Attributes Attribute `json:"attributes"` -} - -type AccountResponse struct { - Data Account `json:"data"` -} - -type ListAccountResponse struct { - Data []Account `json:"data"` - Links Link `json:"links"` -} - -type ListInquiryResponse struct { - Data []Inquiry `json:"data"` - Links Link `json:"links"` + Relationships Relationships `json:"relationships"` } From 3fb8bb58c00ae30875e4b48493edc7c623f0a5b1 Mon Sep 17 00:00:00 2001 From: ____ Date: Sat, 22 Jul 2023 16:00:22 -0700 Subject: [PATCH 04/10] verification --- pkg/internal/persona/types.go | 129 ++++++++++++--------------- pkg/internal/persona/verification.go | 6 ++ 2 files changed, 61 insertions(+), 74 deletions(-) diff --git a/pkg/internal/persona/types.go b/pkg/internal/persona/types.go index 2d0bd29f..00b2044a 100644 --- a/pkg/internal/persona/types.go +++ b/pkg/internal/persona/types.go @@ -13,8 +13,8 @@ type HashValue struct { } type StringValue struct { - Type string `json:"type"` - Value string `json:"value"` + Type string `json:"type"` + Value *string `json:"value"` } type ArrayValue struct { @@ -89,19 +89,19 @@ type Behavior struct { BehaviorThreatLevel string `json:"behaviorThreatLevel"` } -type AccountField struct { - Name HashValue `json:"name"` - Address HashValue `json:"address"` - IdentificationNumbers ArrayValue `json:"identificationNumbers"` - Birthdate Value `json:"birthdate"` - PhoneNumber Value `json:"phoneNumber"` - EmailAddress Value `json:"emailAddress"` - SelfiePhoto Value `json:"selfiePhoto"` +type AccountFields struct { + Name HashValue `json:"name"` + Address HashValue `json:"address"` + IdentificationNumbers ArrayValue `json:"identification_numbers"` + Birthdate Value `json:"birthdate"` + PhoneNumber StringValue `json:"phone_number"` + EmailAddress StringValue `json:"email_address"` + SelfiePhoto Value `json:"selfie_photo"` } -type InquiryField struct { - AddressStreet1 StringValue `json:"addressStreet1"` - AddressStreet2 StringValue `json:"addressStreet2"` +type InquiryFields struct { + AddressStreet1 string `json:"addressStreet1"` + AddressStreet2 string `json:"addressStreet2"` } type Attribute struct { @@ -120,10 +120,11 @@ type Attribute struct { type AccountAttributes struct { Attribute - Fields AccountField `json:"fields"` + Fields AccountFields `json:"fields"` } type CommonFields struct { + Attribute // City of residence address. Not all international addresses use this attribute. AddresCity string `json:"address-city,omitempty"` // Street name of residence address. @@ -153,45 +154,7 @@ type CommonFields struct { SocialSecurityNumber string `json:"social-security-number,omitempty"` } -type InquiryCreationAttributes struct { - AccountId string `json:"account-id"` - CountryCode string `json:"country-code"` - InquityTemplateId string `json:"inquiry-template-id"` - InquityTemplateVersionId string `json:"inquiry-template-version-id"` - // Template ID for flow requirements (use this field if your template ID starts with tmpl_). - // You must pass in either template-id OR inquiry-template-id OR inquiry-template-version-id - TemplateId string `json:"template-id"` - TemplateVersionId string `json:"template-version-id"` - // for styling - ThemeId string `json:"theme-id"` - - Fields CommonFields `json:"fields"` -} - -type InquiryAttributes struct { - Attribute - ReferenceId *string `json:"referenceId"` - Behaviors Behavior `json:"behaviors"` - Notes *string `json:"notes"` - Tags []interface{} `json:"tags"` - PreviousStepName string `json:"previousStepName"` - NextStepName string `json:"nextStepName"` - Fields InquiryField `json:"fields"` -} - -type VerificationAttributes struct { - Attribute - CountryCode *string `json:"countryCode"` - LeftPhotoUrl *string `json:"leftPhotoUrl"` - RightPhotoUrl *string `json:"rightPhotoUrl"` - CenterPhotoUrl *string `json:"centerPhotoUrl"` - PhotoUrls []PhotoURL `json:"photoUrls"` - Checks []Check `json:"checks"` - CaptureMethod string `json:"captureMethod"` -} - -type IncludeAttributes struct { - VerificationAttributes +type CommonAttributes struct { SelfiePhoto *string `json:"selfiePhoto"` SelfiePhotoUrl *string `json:"selfiePhotoUrl"` FrontPhotoUrl *PhotoURL `json:"frontPhotoUrl"` @@ -228,16 +191,30 @@ type IncludeAttributes struct { IdentificationNumber string `json:"identificationNumber"` } -type Included struct { - Id string `json:"id"` - Type string `json:"type"` - Atrributes IncludeAttributes `json:"attributes"` - Relationships Relationships `json:"relationships"` +type InquiryCreationAttributes struct { + AccountId string `json:"account-id"` + CountryCode string `json:"country-code"` + InquityTemplateId string `json:"inquiry-template-id"` + InquityTemplateVersionId string `json:"inquiry-template-version-id"` + // Template ID for flow requirements (use this field if your template ID starts with tmpl_). + // You must pass in either template-id OR inquiry-template-id OR inquiry-template-version-id + TemplateId string `json:"template-id"` + TemplateVersionId string `json:"template-version-id"` + // for styling + ThemeId string `json:"theme-id"` + + Fields CommonFields `json:"fields"` } -type InquiryPayload struct { - AccountId string `json:"accountId"` - Template string `json:"template"` +type InquiryAttributes struct { + Attribute + ReferenceId *string `json:"referenceId"` + Behaviors Behavior `json:"behaviors"` + Notes *string `json:"notes"` + Tags []interface{} `json:"tags"` + PreviousStepName string `json:"previousStepName"` + NextStepName string `json:"nextStepName"` + Fields InquiryFields `json:"fields"` } type CompletedSteps struct { @@ -245,21 +222,25 @@ type CompletedSteps struct { Status string `json:"status"` } -type IdentityVerification struct { - Id string `json:"id"` - Status string `json:"status"` - CreatedAt string `json:"createdAt"` - LastUpdatedAt string `json:"lastUpdatedAt"` - CompletedSteps CompletedSteps `json:"completedSteps"` +type VerificationAttributes struct { + CommonAttributes + CountryCode *string `json:"countryCode"` + LeftPhotoUrl *string `json:"leftPhotoUrl"` + RightPhotoUrl *string `json:"rightPhotoUrl"` + CenterPhotoUrl *string `json:"centerPhotoUrl"` + PhotoUrls []PhotoURL `json:"photoUrls"` + Checks []Check `json:"checks"` + CaptureMethod string `json:"captureMethod"` } -type IdentityVerificationPayload struct { - AccountId string `json:"accountId"` - Template string `json:"template"` +type IncludeAttributes struct { + VerificationAttributes + CommonAttributes } -type Verification struct { - Id string `json:"id"` - Attributes VerificationAttributes `json:"attributes"` - Relationships Relationships `json:"relationships"` +type Included struct { + Id string `json:"id"` + Type string `json:"type"` + Atrributes IncludeAttributes `json:"attributes"` + Relationships Relationships `json:"relationships"` } diff --git a/pkg/internal/persona/verification.go b/pkg/internal/persona/verification.go index d3a2864f..104b8a05 100644 --- a/pkg/internal/persona/verification.go +++ b/pkg/internal/persona/verification.go @@ -32,6 +32,12 @@ Check for the following statuses to monitor progress and find completed results. */ +type Verification struct { + Id string `json:"id"` + Attributes VerificationAttributes `json:"attributes"` + Relationships Relationships `json:"relationships"` +} + func (c *PersonaClient) GetVerificationById(id string) (*Verification, error) { verification := &Verification{} err := c.doRequest(http.MethodGet, "/v1/verifications/"+id, nil, verification) From 3b7d6c2d80530219c49a7aa8400117060ae96bb0 Mon Sep 17 00:00:00 2001 From: ____ Date: Sat, 22 Jul 2023 22:54:07 -0700 Subject: [PATCH 05/10] made some modifications --- pkg/internal/persona/inquiries.go | 16 ++-- pkg/internal/persona/types.go | 132 +++++++++++++++--------------- 2 files changed, 70 insertions(+), 78 deletions(-) diff --git a/pkg/internal/persona/inquiries.go b/pkg/internal/persona/inquiries.go index 6475b305..71795e77 100644 --- a/pkg/internal/persona/inquiries.go +++ b/pkg/internal/persona/inquiries.go @@ -43,17 +43,13 @@ type InquiryResponse struct { Included []Included `json:"included"` } -type InquiryListItem struct { - Data Inquiry `json:"data"` -} - type ListInquiryResponse struct { - Data []InquiryListItem `json:"data"` - Links Link `json:"links"` + Data []Inquiry `json:"data"` + Links Link `json:"links"` } -func (c *PersonaClient) CreateInquiry(request InquiryCreateRequest) (*Inquiry, error) { - inquiry := &Inquiry{} +func (c *PersonaClient) CreateInquiry(request InquiryCreateRequest) (*InquiryResponse, error) { + inquiry := &InquiryResponse{} err := c.doRequest(http.MethodPost, "/v1/inquiries", request, inquiry) if err != nil { return nil, common.StringError(err, "failed to create inquiry") @@ -61,8 +57,8 @@ func (c *PersonaClient) CreateInquiry(request InquiryCreateRequest) (*Inquiry, e return inquiry, nil } -func (c *PersonaClient) GetInquiryById(id string) (*Inquiry, error) { - inquiry := &Inquiry{} +func (c *PersonaClient) GetInquiryById(id string) (*InquiryResponse, error) { + inquiry := &InquiryResponse{} err := c.doRequest(http.MethodGet, "/v1/inquiries/"+id, nil, inquiry) if err != nil { return nil, common.StringError(err, "failed to get inquiry") diff --git a/pkg/internal/persona/types.go b/pkg/internal/persona/types.go index 00b2044a..0223c983 100644 --- a/pkg/internal/persona/types.go +++ b/pkg/internal/persona/types.go @@ -92,16 +92,16 @@ type Behavior struct { type AccountFields struct { Name HashValue `json:"name"` Address HashValue `json:"address"` - IdentificationNumbers ArrayValue `json:"identification_numbers"` + IdentificationNumbers ArrayValue `json:"identificationNumbers"` Birthdate Value `json:"birthdate"` - PhoneNumber StringValue `json:"phone_number"` - EmailAddress StringValue `json:"email_address"` - SelfiePhoto Value `json:"selfie_photo"` + PhoneNumber StringValue `json:"phoneNumber"` + EmailAddress StringValue `json:"emailAddress"` + SelfiePhoto Value `json:"selfiePhoto"` } type InquiryFields struct { - AddressStreet1 string `json:"addressStreet1"` - AddressStreet2 string `json:"addressStreet2"` + AddressStreet1 StringValue `json:"addressStreet1"` + AddressStreet2 StringValue `json:"addressStreet2"` } type Attribute struct { @@ -126,82 +126,82 @@ type AccountAttributes struct { type CommonFields struct { Attribute // City of residence address. Not all international addresses use this attribute. - AddresCity string `json:"address-city,omitempty"` + AddresCity string `json:"addressCity,omitempty"` // Street name of residence address. - AddressStreet1 string `json:"address-street-1,omitempty"` + AddressStreet1 string `json:"addressStreet1,omitempty"` // Extension of residence address, usually apartment or suite number. - AddressStreet2 string `json:"address-street-2,omitempty"` + AddressStreet2 string `json:"addressStreet2,omitempty"` // State or subdivision of residence address. In the US, // this should be the unabbreviated name. Not all international addresses use this attribute. - AddressSubdivision string `json:"address-subdivision,omitempty"` + AddressSubdivision string `json:"addressSubdivision,omitempty"` // Postal code of residence address. Not all international addresses use this attribute. - AddressPostalCode string `json:"address-postal-code,omitempty"` + AddressPostalCode string `json:"addressPostalCode,omitempty"` // Birthdate, must be in the format "YYYY-MM-DD". Birthdate string `json:"birthdate,omitempty"` // ISO 3166-1 alpha 2 country code of the government ID to be verified. This is generally their country of residence as well. - CountryCode string `json:"country-code,omitempty"` + CountryCode string `json:"countryCode,omitempty"` - EmailAddress string `json:"email-address,omitempty"` + EmailAddress string `json:"emailAddress,omitempty"` // Given or first name. - NameFirst string `json:"name-first,omitempty"` + NameFirst string `json:"nameFirst,omitempty"` // Family or last name. - NameLast string `json:"name-last,omitempty"` + NameLast string `json:"nameLast,omitempty"` - NameMiddle string `json:"name-middle,omitempty"` + NameMiddle string `json:"nameMiddle,omitempty"` - PhoneNumber string `json:"phone-number,omitempty"` + PhoneNumber string `json:"phoneNumber,omitempty"` - SocialSecurityNumber string `json:"social-security-number,omitempty"` + SocialSecurityNumber string `json:"socialSecurityNumber,omitempty"` } type CommonAttributes struct { - SelfiePhoto *string `json:"selfiePhoto"` - SelfiePhotoUrl *string `json:"selfiePhotoUrl"` - FrontPhotoUrl *PhotoURL `json:"frontPhotoUrl"` - BackPhotoUrl *PhotoURL `json:"backPhotoUrl"` - VideoUrl *string `json:"videoUrl"` - IdClass string `json:"idClass"` - CaptureMethod string `json:"captureMethod"` - EntityConfidenceScore int `json:"entityConfidenceScore"` - EntityConfidenceReasons []string `json:"entityConfidenceReasons"` - NameFirst string `json:"nameFirst"` - NameMiddle *string `json:"nameMiddle"` - NameLast string `json:"nameLast"` - NameSuffix *string `json:"nameSuffix"` - Birthdate string `json:"birthdate"` - AddressStreet1 string `json:"addressStreet1"` - AddressStreet2 *string `json:"addressStreet2"` - AddressCity string `json:"addressCity"` - AddressSubdivision string `json:"addressSubdivision"` - AddressPostalCode string `json:"addressPostalCode"` - IssuingAuthority string `json:"issuingAuthority"` - IssuingSubdivision string `json:"issuingSubdivision"` - Nationality *string `json:"nationality"` - DocumentNumber *string `json:"documentNumber"` - VisaStatus *string `json:"visaStatus"` - IssueDate string `json:"issueDate"` - ExpirationDate string `json:"expirationDate"` - Designations *string `json:"designations"` - Birthplace *string `json:"birthplace"` - Endorsements *string `json:"endorsements"` - Height *string `json:"height"` - Sex string `json:"sex"` - Restrictions *string `json:"restrictions"` - VehicleClass *string `json:"vehicleClass"` - IdentificationNumber string `json:"identificationNumber"` + SelfiePhoto *string `json:"selfiePhoto"` + SelfiePhotoUrl *string `json:"selfiePhotoUrl"` + FrontPhotoUrl *string `json:"frontPhotoUrl"` + BackPhotoUrl *string `json:"backPhotoUrl"` + VideoUrl *string `json:"videoUrl"` + IdClass string `json:"idClass"` + CaptureMethod string `json:"captureMethod"` + EntityConfidenceScore float64 `json:"entityConfidenceScore"` + EntityConfidenceReasons []string `json:"entityConfidenceReasons"` + NameFirst string `json:"nameFirst"` + NameMiddle *string `json:"nameMiddle"` + NameLast string `json:"nameLast"` + NameSuffix *string `json:"nameSuffix"` + Birthdate string `json:"birthdate"` + AddressStreet1 string `json:"addressStreet1"` + AddressStreet2 *string `json:"addressStreet2"` + AddressCity string `json:"addressCity"` + AddressSubdivision string `json:"addressSubdivision"` + AddressPostalCode string `json:"addressPostalCode"` + IssuingAuthority string `json:"issuingAuthority"` + IssuingSubdivision string `json:"issuingSubdivision"` + Nationality *string `json:"nationality"` + DocumentNumber *string `json:"documentNumber"` + VisaStatus *string `json:"visaStatus"` + IssueDate string `json:"issueDate"` + ExpirationDate string `json:"expirationDate"` + Designations *string `json:"designations"` + Birthplace *string `json:"birthplace"` + Endorsements *string `json:"endorsements"` + Height *string `json:"height"` + Sex string `json:"sex"` + Restrictions *string `json:"restrictions"` + VehicleClass *string `json:"vehicleClass"` + IdentificationNumber string `json:"identificationNumber"` } type InquiryCreationAttributes struct { - AccountId string `json:"account-id"` - CountryCode string `json:"country-code"` - InquityTemplateId string `json:"inquiry-template-id"` - InquityTemplateVersionId string `json:"inquiry-template-version-id"` + AccountId string `json:"accountId"` + CountryCode string `json:"countryCode"` + InquityTemplateId string `json:"inquiryTemplateId"` + InquityTemplateVersionId string `json:"inquiryTemplateVersionId"` // Template ID for flow requirements (use this field if your template ID starts with tmpl_). // You must pass in either template-id OR inquiry-template-id OR inquiry-template-version-id - TemplateId string `json:"template-id"` - TemplateVersionId string `json:"template-version-id"` + TemplateId string `json:"templateId"` + TemplateVersionId string `json:"templateVersionId"` // for styling - ThemeId string `json:"theme-id"` + ThemeId string `json:"themeId"` Fields CommonFields `json:"fields"` } @@ -223,6 +223,7 @@ type CompletedSteps struct { } type VerificationAttributes struct { + Attribute CommonAttributes CountryCode *string `json:"countryCode"` LeftPhotoUrl *string `json:"leftPhotoUrl"` @@ -233,14 +234,9 @@ type VerificationAttributes struct { CaptureMethod string `json:"captureMethod"` } -type IncludeAttributes struct { - VerificationAttributes - CommonAttributes -} - type Included struct { - Id string `json:"id"` - Type string `json:"type"` - Atrributes IncludeAttributes `json:"attributes"` - Relationships Relationships `json:"relationships"` + Id string `json:"id"` + Type string `json:"type"` + Atrributes VerificationAttributes `json:"attributes"` + Relationships Relationships `json:"relationships"` } From 656201044c814a4fac00e94f68c85e03052604b7 Mon Sep 17 00:00:00 2001 From: ____ Date: Sat, 22 Jul 2023 23:18:33 -0700 Subject: [PATCH 06/10] get verification --- pkg/internal/persona/client.go | 1 - pkg/internal/persona/verification.go | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/internal/persona/client.go b/pkg/internal/persona/client.go index 28934414..c4b60320 100644 --- a/pkg/internal/persona/client.go +++ b/pkg/internal/persona/client.go @@ -64,6 +64,5 @@ func (c *PersonaClient) doRequest(method, url string, payload, result interface{ return fmt.Errorf("failed to decode response: %w", err) } } - return nil } diff --git a/pkg/internal/persona/verification.go b/pkg/internal/persona/verification.go index 104b8a05..2dbdbd75 100644 --- a/pkg/internal/persona/verification.go +++ b/pkg/internal/persona/verification.go @@ -38,8 +38,12 @@ type Verification struct { Relationships Relationships `json:"relationships"` } -func (c *PersonaClient) GetVerificationById(id string) (*Verification, error) { - verification := &Verification{} +type VerificationResponse struct { + Data Verification `json:"data"` +} + +func (c *PersonaClient) GetVerificationById(id string) (*VerificationResponse, error) { + verification := &VerificationResponse{} err := c.doRequest(http.MethodGet, "/v1/verifications/"+id, nil, verification) if err != nil { return nil, common.StringError(err, "failed to get verification by id") From 513215a3ec5dd42a7ce5f073a1a5c1dfca0d95bc Mon Sep 17 00:00:00 2001 From: ____ Date: Sun, 23 Jul 2023 00:01:34 -0700 Subject: [PATCH 07/10] tests --- .env.example | 1 + config/config.go | 1 + .../persona/persona_integration_test.go | 67 +++++++++++++++++++ pkg/internal/persona/persona_test.go | 57 +++++----------- 4 files changed, 86 insertions(+), 40 deletions(-) diff --git a/.env.example b/.env.example index f6331757..d3a418c7 100644 --- a/.env.example +++ b/.env.example @@ -50,3 +50,4 @@ RECEIPTS_EMAIL_ADDRESS=receipts@stringxyz.com CARD_FAIL_PROBABILITY= [0.0 - 1.0] WEBHOOK_SECRET_KEY=secret SLACK_WEBHOOK_URL=https://hooks.slack.com/services/<>/<> +PERSONA_API_KEY= diff --git a/config/config.go b/config/config.go index b41010b8..92c12ed2 100644 --- a/config/config.go +++ b/config/config.go @@ -50,6 +50,7 @@ type vars struct { RECEIPTS_EMAIL_ADDRESS string `required:"true"` WEBHOOK_SECRET_KEY string `required:"true"` SLACK_WEBHOOK_URL string `required:"true"` + PERSONA_API_KEY string `required:"true"` } var Var vars diff --git a/pkg/internal/persona/persona_integration_test.go b/pkg/internal/persona/persona_integration_test.go index fd7349d8..0e305e45 100644 --- a/pkg/internal/persona/persona_integration_test.go +++ b/pkg/internal/persona/persona_integration_test.go @@ -2,3 +2,70 @@ // +build integration package persona + +import ( + "fmt" + "testing" + + env "github.com/String-xyz/go-lib/v2/config" + "github.com/stretchr/testify/assert" + + "github.com/String-xyz/string-api/config" +) + +func init() { + err := env.LoadEnv(&config.Var, "../../../.env") + if err != nil { + fmt.Printf("error loading env: %v", err) + } +} + +func client() *PersonaClient { + return New(config.Var.PERSONA_API_KEY) +} + +func TestIntegrationCreateAccount(t *testing.T) { + request := AccountCreateRequest{AccountCreate{Attributes: CommonFields{NameFirst: "Mister", NameLast: "Tester"}}} + account, err := client().CreateAccount(request) + assert.NoError(t, err) + assert.NotNil(t, account) +} + +func TestIntegrationGetAccount(t *testing.T) { + account, err := client().GetAccountById("act_Q1zEPYBZ6Qx8qJKcMrwDXxVA") + assert.NoError(t, err) + assert.NotNil(t, account) +} + +func TestIntegrationListAccounts(t *testing.T) { + accounts, err := client().ListAccounts() + assert.NoError(t, err) + assert.NotNil(t, accounts) + assert.NotEmpty(t, accounts.Data) +} + +func TestIntegrationCreateInquiry(t *testing.T) { + request := InquiryCreateRequest{InquiryCreate{Attributes: InquiryCreationAttributes{AccountId: "act_sx5fkrKCzoAaddKYBhWckcE6"}}} + inquiry, err := client().CreateInquiry(request) + assert.NoError(t, err) + assert.NotNil(t, inquiry) +} + +func TestIntegrationGetInquiry(t *testing.T) { + inquiry, err := client().GetInquiryById("inq_kmXCg5pLzWTwg2LuAjiaBsoC") + assert.NoError(t, err) + assert.NotNil(t, inquiry) +} + +func TestIntegrationListInquiriesByAccount(t *testing.T) { + inquiries, err := client().ListInquiriesByAccount("act_ndJNqdhWNi44S4Twf4bqzod1") + assert.NoError(t, err) + assert.NotNil(t, inquiries) + assert.NotEmpty(t, inquiries.Data) +} + +func TestIntegrationGetVerification(t *testing.T) { + verification, err := client().GetVerificationById("ver_ww2rkwtA6c9FiuCG8Jsk1DJt") + assert.NoError(t, err) + assert.NotNil(t, verification) +} diff --git a/pkg/internal/persona/persona_test.go b/pkg/internal/persona/persona_test.go index dbedcbaf..43a464c1 100644 --- a/pkg/internal/persona/persona_test.go +++ b/pkg/internal/persona/persona_test.go @@ -1,6 +1,3 @@ -//go:build unit -// +build unit - package persona import ( @@ -53,53 +50,32 @@ func TestGetVerificationById(t *testing.T) { assert.NotNil(t, v) } -func TestGetTemplates(t *testing.T) { - testServer := testServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - res.Write([]byte(`[{"id":"test-template"}]`)) - })) - defer func() { testServer.Close() }() - - c := New("test-key") - c.BaseURL = testServer.URL - - te, err := c.GetTemplates() - if err != nil { - t.Errorf("Expected no error, got %v", err) - } - - if len(te) != 1 { - t.Errorf("Expected one template, got %v", len(te)) - } -} - func TestCreateInquiry(t *testing.T) { server := testServer(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/v1/inquiries", r.URL.String()) assert.Equal(t, http.MethodPost, r.Method) - var payload InquiryPayload + var payload InquiryCreateRequest err := json.NewDecoder(r.Body).Decode(&payload) assert.NoError(t, err) - assert.Equal(t, "test-account", payload.AccountId) - assert.Equal(t, "test-template", payload.Template) + assert.Equal(t, "test-account", payload.Data.Attributes.AccountId) + assert.Equal(t, "test-template", payload.Data.Attributes.TemplateId) - inquiry := &Inquiry{ - Id: "test-id", - Status: "completed", - } + inquiry := &InquiryResponse{Data: Inquiry{ + Id: "test-id", + Type: "inquiry", + }} json.NewEncoder(w).Encode(inquiry) }) defer server.Close() client := NewPersonaClient(server.URL, "test-key") - inquiry, err := client.CreateInquiry(InquiryPayload{ - AccountId: "test-account", - Template: "test-template", - }) + inquiry, err := client.CreateInquiry(InquiryCreateRequest{ + InquiryCreate{InquiryCreationAttributes{AccountId: "test-account", TemplateId: "test-template"}}}) assert.NoError(t, err) - assert.Equal(t, "test-id", inquiry.Id) - assert.Equal(t, "completed", inquiry.Status) + assert.Equal(t, "test-id", inquiry.Data.Id) + assert.Equal(t, "inquiry", inquiry.Data.Type) } func TestGetInquiry(t *testing.T) { @@ -107,9 +83,10 @@ func TestGetInquiry(t *testing.T) { assert.Equal(t, "/v1/inquiries/test-id", r.URL.String()) assert.Equal(t, http.MethodGet, r.Method) - inquiry := &Inquiry{ - Id: "test-id", - Status: "completed", + inquiry := &InquiryResponse{Data: Inquiry{ + Id: "test-id", + Type: "inquiry", + }, } json.NewEncoder(w).Encode(inquiry) @@ -119,6 +96,6 @@ func TestGetInquiry(t *testing.T) { client := NewPersonaClient(server.URL, "test-key") inquiry, err := client.GetInquiryById("test-id") assert.NoError(t, err) - assert.Equal(t, "test-id", inquiry.Id) - assert.Equal(t, "completed", inquiry.Status) + assert.Equal(t, "test-id", inquiry.Data.Id) + assert.Equal(t, "inquiry", inquiry.Data.Type) } From 055a3a14bfb85a679e7a8e4ce45aa43cb669d111 Mon Sep 17 00:00:00 2001 From: ____ Date: Sun, 23 Jul 2023 10:06:02 -0700 Subject: [PATCH 08/10] pass test --- pkg/internal/persona/persona_test.go | 9 +++++++-- pkg/internal/persona/verification.go | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/internal/persona/persona_test.go b/pkg/internal/persona/persona_test.go index 43a464c1..00253994 100644 --- a/pkg/internal/persona/persona_test.go +++ b/pkg/internal/persona/persona_test.go @@ -36,8 +36,13 @@ func TestDoRequest(t *testing.T) { } func TestGetVerificationById(t *testing.T) { - testServer := testServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - res.Write([]byte(`[{"id":"test-verification"}]`)) + testServer := testServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + verification := &VerificationResponse{Data: Verification{ + Id: "test-id", + Type: "verification", + }} + + json.NewEncoder(w).Encode(verification) })) defer func() { testServer.Close() }() diff --git a/pkg/internal/persona/verification.go b/pkg/internal/persona/verification.go index 2dbdbd75..6b8efa93 100644 --- a/pkg/internal/persona/verification.go +++ b/pkg/internal/persona/verification.go @@ -34,6 +34,7 @@ Check for the following statuses to monitor progress and find completed results. type Verification struct { Id string `json:"id"` + Type string `json:"type"` Attributes VerificationAttributes `json:"attributes"` Relationships Relationships `json:"relationships"` } From 8a607539457cdd77c3fd19dd9658f67402a7deae Mon Sep 17 00:00:00 2001 From: ____ Date: Sun, 23 Jul 2023 10:18:05 -0700 Subject: [PATCH 09/10] one more unit --- pkg/internal/persona/persona_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pkg/internal/persona/persona_test.go b/pkg/internal/persona/persona_test.go index 00253994..ae7da1f2 100644 --- a/pkg/internal/persona/persona_test.go +++ b/pkg/internal/persona/persona_test.go @@ -35,6 +35,33 @@ func TestDoRequest(t *testing.T) { } } +func TestCreateAccount(t *testing.T) { + testServer := testServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/v1/accounts", r.URL.String()) + assert.Equal(t, http.MethodPost, r.Method) + + var payload AccountCreateRequest + err := json.NewDecoder(r.Body).Decode(&payload) + assert.NoError(t, err) + assert.Equal(t, "Testable", payload.Data.Attributes.NameFirst) + assert.Equal(t, "Testerson", payload.Data.Attributes.NameLast) + + account := &AccountResponse{Data: Account{ + Id: "test-id", + Type: "account", + }} + + json.NewEncoder(w).Encode(account) + })) + defer func() { testServer.Close() }() + c := New("test-key") + c.BaseURL = testServer.URL + v, err := c.CreateAccount(AccountCreateRequest{Data: AccountCreate{Attributes: CommonFields{NameFirst: "Testable", NameLast: "Testerson"}}}) + assert.NoError(t, err) + assert.NotNil(t, v) + assert.Equal(t, "test-id", v.Data.Id) +} + func TestGetVerificationById(t *testing.T) { testServer := testServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { verification := &VerificationResponse{Data: Verification{ From ed394f9808c0ec72c67162f4cebcbd248880624c Mon Sep 17 00:00:00 2001 From: ____ Date: Tue, 25 Jul 2023 12:33:59 -0700 Subject: [PATCH 10/10] fixed an issue with creating an enquiry --- pkg/internal/persona/persona_integration_test.go | 2 +- pkg/internal/persona/types.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/internal/persona/persona_integration_test.go b/pkg/internal/persona/persona_integration_test.go index 0e305e45..049cc69d 100644 --- a/pkg/internal/persona/persona_integration_test.go +++ b/pkg/internal/persona/persona_integration_test.go @@ -45,7 +45,7 @@ func TestIntegrationListAccounts(t *testing.T) { } func TestIntegrationCreateInquiry(t *testing.T) { - request := InquiryCreateRequest{InquiryCreate{Attributes: InquiryCreationAttributes{AccountId: "act_sx5fkrKCzoAaddKYBhWckcE6"}}} + request := InquiryCreateRequest{InquiryCreate{Attributes: InquiryCreationAttributes{AccountId: "act_ndJNqdhWNi44S4Twf4bqzod1", InquityTemplateId: "itmpl_z2so7W2bCFHELp2dhxqqQjGy"}}} inquiry, err := client().CreateInquiry(request) assert.NoError(t, err) assert.NotNil(t, inquiry) diff --git a/pkg/internal/persona/types.go b/pkg/internal/persona/types.go index 0223c983..84b804a1 100644 --- a/pkg/internal/persona/types.go +++ b/pkg/internal/persona/types.go @@ -203,7 +203,7 @@ type InquiryCreationAttributes struct { // for styling ThemeId string `json:"themeId"` - Fields CommonFields `json:"fields"` + Fields *CommonFields `json:"fields"` } type InquiryAttributes struct {