Skip to content

persona api wrapper #221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ [email protected]
CARD_FAIL_PROBABILITY= [0.0 - 1.0]
WEBHOOK_SECRET_KEY=secret
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/<>/<>
PERSONA_API_KEY=
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
62 changes: 62 additions & 0 deletions pkg/internal/persona/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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.
*/

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 account")
}
return account, 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
}
68 changes: 68 additions & 0 deletions pkg/internal/persona/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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{},
}
Comment on lines +18 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
return &PersonaClient{
APIKey: apiKey,
BaseURL: "https://withpersona.com/api/",
Client: &http.Client{},
}
return NewPersonaClient("https://withpersona.com/api/", apiKey, &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")
req.Header.Set("Key-Inflection", "camel")

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
}
76 changes: 76 additions & 0 deletions pkg/internal/persona/inquiries.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package persona

import (
"net/http"

"github.com/String-xyz/go-lib/v2/common"
)

/*
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.

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Pending - The individual submitted a verification within the inquiry.
* Pending - The individual submitted one or more verifications 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.
*/

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 ListInquiryResponse struct {
Data []Inquiry `json:"data"`
Links Link `json:"links"`
}

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")
}
return inquiry, nil
}

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")
}
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
}
71 changes: 71 additions & 0 deletions pkg/internal/persona/persona_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//go:build integration
// +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_ndJNqdhWNi44S4Twf4bqzod1", InquityTemplateId: "itmpl_z2so7W2bCFHELp2dhxqqQjGy"}}}
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)
}
Loading