Skip to content
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

Add Support for Letters #16

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
92 changes: 80 additions & 12 deletions addresses.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package lob

import (
"errors"
"fmt"
"strconv"
"strings"
)
Expand Down Expand Up @@ -86,7 +87,7 @@ func (lob *lob) ListAddresses(count int) (*ListAddressesResponse, error) {

resp := new(ListAddressesResponse)
if err := lob.get("addresses/", map[string]string{
"limit": strconv.Itoa(count),
"limit": strconv.Itoa(count),
}, resp); err != nil {
return nil, err
}
Expand All @@ -105,18 +106,31 @@ type USAddressVerificationRequest struct {

// USAddressVerificationResponse gives the response from attempting to verify a US address.
type USAddressVerificationResponse struct {
Id string `json:"id"`
Recipient string `json:"recipient"`
PrimaryLine string `json:"primary_line"`
SecondaryLine string `json:"secondary_line"`
Urbanization string `json:"urbanization,omitempty"`
LastLine string `json:"last_line"`
Deliverability string `json:"deliverability"`
Components USAddressComponents `json:"components"`
DeliverabilityAnalysis USAddressDeliverabilityAnalysis `json:"deliverability_analysis"`
Object string `json:"object"`
Id string `json:"id"`
Recipient string `json:"recipient"`
PrimaryLine string `json:"primary_line"`
SecondaryLine string `json:"secondary_line"`
Urbanization string `json:"urbanization,omitempty"`
LastLine string `json:"last_line"`
Deliverability USAddressVerificationDeliverability `json:"deliverability"`
Components USAddressComponents `json:"components"`
DeliverabilityAnalysis USAddressDeliverabilityAnalysis `json:"deliverability_analysis"`
Object string `json:"object"`
}

//USAddressVerificationDeliverability is the type for the deliverability of an address verified
type USAddressVerificationDeliverability string

//list of USAddressVerificationDeliverability values
var (
USAddressVerificationDeliverabilityDeliverable USAddressVerificationDeliverability = "deliverable"
USAddressVerificationDeliverabilityUnnecessaryUnit USAddressVerificationDeliverability = "deliverable_unnecessary_unit"
USAddressVerificationDeliverabilityIncorrectUnit USAddressVerificationDeliverability = "deliverable_incorrect_unit"
USAddressVerificationDeliverabilitydMissingUnit USAddressVerificationDeliverability = "deliverable_missing_unit"
USAddressVerificationDeliverabilityUndeliverable USAddressVerificationDeliverability = "undeliverable"
)

//USAddressComponents are the components which make up a US address
type USAddressComponents struct {
PrimaryNumber string `json:"primary_number"`
StreetPredirection string `json:"street_predirection"`
Expand Down Expand Up @@ -157,6 +171,10 @@ type USAddressDeliverabilityAnalysis struct {
SuiteReturnCode string `json:"suite_return_code"`
}

// with special codes you can get a proper response back in test mode; this magic secondary line means we didn't
// request it with a special code so fallback to the old behavior
const testFillInLine2Required = "See Https://www.lob.com/docs#us-verification-test-environment For More Info"

// VerifyUSAddress verifies the given US address and returns the validation results.
func (lob *lob) VerifyUSAddress(address *Address) (*USAddressVerificationResponse, error) {
req := USAddressVerificationRequest{
Expand All @@ -173,7 +191,57 @@ func (lob *lob) VerifyUSAddress(address *Address) (*USAddressVerificationRespons
}

// in test, fill in components
if strings.HasPrefix(lob.APIKey, "test") {
if strings.HasPrefix(lob.APIKey, "test") && resp.SecondaryLine == testFillInLine2Required {
streetSplit := strings.Split(address.AddressLine1, " ")
if len(streetSplit) > 2 {
resp.Components.PrimaryNumber = streetSplit[0]
resp.Components.StreetName = streetSplit[1]
resp.Components.StreetSuffix = streetSplit[2]
}
if address.AddressLine2 != nil {
resp.Components.SecondaryNumber = *address.AddressLine2
}
if address.AddressZip != nil {
resp.Components.ZipCode = *address.AddressZip
}
if address.AddressCity != nil {
resp.Components.City = *address.AddressCity
}
if address.AddressState != nil {
resp.Components.State = *address.AddressState
}
}

return resp, nil
}

//AddressVerificationRequestCasing states how the verified address should be returned
type AddressVerificationRequestCasing string

//possible values of AddressVerificationRequestCasing
var (
AddressVerificationRequestCasingUpper AddressVerificationRequestCasing = "upper"
AddressVerificationRequestCasingProper AddressVerificationRequestCasing = "proper"
)

// VerifyUSAddress verifies the given US address and returns the validation results.
func (lob *lob) VerifyUSAddressWithCasing(address *Address, casing AddressVerificationRequestCasing) (*USAddressVerificationResponse, error) {
req := USAddressVerificationRequest{
Recipient: address.Name,
AddressLine1: String(address.AddressLine1),
AddressLine2: address.AddressLine2,
AddressCity: address.AddressCity,
AddressState: address.AddressState,
AddressZip: address.AddressZip,
}

resp := new(USAddressVerificationResponse)
if err := lob.post(fmt.Sprintf("us_verifications?case=%s", casing), json2form(req), resp); err != nil {
return nil, err
}

// in test, fill in components
if strings.HasPrefix(lob.APIKey, "test") && resp.SecondaryLine == testFillInLine2Required {
streetSplit := strings.Split(address.AddressLine1, " ")
if len(streetSplit) > 2 {
resp.Components.PrimaryNumber = streetSplit[0]
Expand Down
43 changes: 14 additions & 29 deletions checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,36 +33,21 @@ type Check struct {
URL string `json:"url"`
}

// Tracking provides information on shipment tracking for a check.
type Tracking struct {
Carrier string `json:"carrier"`
Events []interface{} `json:"events"`
ID string `json:"id"`
Link *string `json:"link"`
Object string `json:"object"`
TrackingNumber string `json:"tracking_number"`
}

// Mail types that lob supports.
const (
MailTypeUspsFirstClass = "usps_first_class"
MailTypeUpsNextDayAir = "ups_next_day_air"
)

// CreateCheckRequest specifies options for creating a check.
type CreateCheckRequest struct {
Amount float64 `json:"amount"`
BankAccountID string `json:"bank_account"`
CheckBottom *string `json:"check_bottom"` // 400 chars, at bottom (cannot use with message)
CheckNumber *string `json:"check_number"`
Data map[string]string `json:"data"`
Description *string `json:"description"`
FromAddressID string `json:"from"`
Logo *string `json:"logo"` // url or multiform. Square, RGB / CMYK, >= 100x100, transparent bg, PNG or JPEG, and will be grayscaled
MailType *string `json:"mail_type"`
Memo *string `json:"memo"` // 40 chars in memo line
Message *string `json:"message"` // 400 chars, at top (cannot use with check_bottom)
ToAddressID string `json:"to"`
Amount float64 `json:"amount"`
BankAccountID string `json:"bank_account"`
CheckBottom *string `json:"check_bottom"` // 400 chars, at bottom (cannot use with message)
CheckNumber *string `json:"check_number"`
Data map[string]string `json:"data"`
Description *string `json:"description"`
FromAddressID string `json:"from"`
Logo *string `json:"logo"` // url or multiform. Square, RGB / CMYK, >= 100x100, transparent bg, PNG or JPEG, and will be grayscaled
MailType *string `json:"mail_type"`
Memo *string `json:"memo"` // 40 chars in memo line
Message *string `json:"message"` // 400 chars, at top (cannot use with check_bottom)
ToAddressID string `json:"to"`
BillingGroupID *string `json:"billing_group_id"`
}

// CreateCheck requests for a new check to be printed and mailed.
Expand Down Expand Up @@ -113,7 +98,7 @@ func (lob *lob) ListChecks(count int) (*ListChecksResponse, error) {

resp := new(ListChecksResponse)
if err := lob.get("checks", map[string]string{
"limit": strconv.Itoa(count),
"limit": strconv.Itoa(count),
}, resp); err != nil {
return nil, err
}
Expand Down
163 changes: 163 additions & 0 deletions letters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package lob

import (
"strconv"
"time"
)

//#region model definition

// Letter represents a letter in lob's system
type Letter struct {
Error *Error `json:"error"`
ID string `json:"id"`
Description *string `json:"description"`
Metadata map[string]string `json:"metadata"`
To *Address `json:"to"`
From *Address `json:"from"`
Color bool `json:"color"`
DoubleSided bool `json:"double_sided"`
AddressPlacement LetterAddressPlacement `json:"address_placement"`
ReturnEnvelope bool `json:"return_envelope"`
PerforatedPage *uint64 `json:"perforated_page"`
CustomEnvelope *CustomEnvelope `json:"custom_envelope"`
ExtraService *LetterExtraService `json:"extra_service"`
MailType *string `json:"mail_type"` //MailTypeUspsFirstClass or MailTypeUspsStandard
URL string `json:"url"`
MergeVariables map[string]string `json:"merge_variables"`
TemplateID *string `json:"template_id"`
TemplateVersionID *string `json:"template_version_id"`
Carrier string `json:"carrier"` //value is USPS
TrackingNumber *string `json:"tracking_number"`
TrackingEvents []TrackingEvent `json:"tracking_events"`
Thumbnails []LetterThumbnail `json:"thumbnails"`
ExpectedDeliveryDate string `json:"expected_delivery_date"`
DateCreated time.Time `json:"date_created"`
DateModified time.Time `json:"date_modified"`
SendDate time.Time `json:"send_date"`
Deleted bool `json:"deleted"`
Object string `json:"object"` //value will always be letter
}

//CreateLetterRequest is the object for creating a new letter
type CreateLetterRequest struct {
Description *string `json:"description"` //must be no longer than 255 characters
To Address `json:"to"` //if you need the id, simply do lob.Address{ID: "id-here"}
From Address `json:"from"` //if you need the id, simply do lob.Address{ID: "id-here"}
BillingGroupID *string `json:"billing_group_id"`
SendDate *time.Time `json:"send_date"`
Color bool `json:"color"`
File string `json:"file"` //please see lob's create letter documentation: https://lob.com/docs#letters_create
DoubleSided bool `json:"double_sided"`
AddressPlacement LetterAddressPlacement `json:"address_placement"`
MailType *string `json:"mail_type"` //MailTypeUspsFirstClass or MailTypeUspsStandard
ExtraService *LetterExtraService `json:"extra_service"`
ReturnEnvelope bool `json:"return_envelope"`
PerforatedPage *uint64 `json:"perforated_page"`
CustomEnvelope *CustomEnvelope `json:"custom_envelope"`
MergeVariables map[string]string `json:"merge_variables"`
Metadata map[string]string `json:"metadata"`
}

//LetterAddressPlacement represents where the address should be placed on the letter
type LetterAddressPlacement string

var (
//LetterAddressPlacementTopFirstPage is for being printed at the top of the first page
LetterAddressPlacementTopFirstPage LetterAddressPlacement = "top_first_page"
//LetterAddressPlacementInsertBlankPage is for inserting a blank page (does cost extra)
LetterAddressPlacementInsertBlankPage LetterAddressPlacement = "insert_blank_page"
)

// CustomEnvelope represents a custom envelope in lob's system
type CustomEnvelope struct {
ID string `json:"id"`
URL string `json:"url"`
Object string `json:"object"` //value should always be "envelope"
}

//LetterExtraService represents the type of extra service requested
type LetterExtraService string

var (
//LetterExtraServiceCertified is for certified mail
LetterExtraServiceCertified LetterExtraService = "certified"
//LetterExtraServiceCertifiedReturnReceipt is for certified mail with return receipt
LetterExtraServiceCertifiedReturnReceipt LetterExtraService = "certified_return_receipt"
//LetterExtraServiceRegistered is for registered mail
LetterExtraServiceRegistered LetterExtraService = "registered"
)

//LetterThumbnail represents the thumbnails of the generated letter
type LetterThumbnail struct {
Small string `json:"small"`
Medium string `json:"medium"`
Large string `json:"large"`
}

//ListLettersResponse details all of the letters we've ever mailed and printed
type ListLettersResponse struct {
Data []Letter `json:"data"`
Object string `json:"object"` //value will always be letter
NextURL string `json:"next_url"`
PreviousURL string `json:"previous_url"`
Count int `json:"count"`
}

//CancelLetterResponse is the response returned when deleting a letter
type CancelLetterResponse struct {
ID string `json:"id"`
Deleted bool `json:"deleted"`
}

//#endregion model definition

// CreateLetter requests for a new letter to be printed and mailed.
func (lob *lob) CreateLetter(req CreateLetterRequest) (*Letter, error) {
resp := new(Letter)

if err := lob.post("letters", json2form(req), resp); err != nil {
return resp, err
}

return resp, nil
}

// GetLetter gets information about a particulr letter.
func (lob *lob) GetLetter(id string) (*Letter, error) {
resp := new(Letter)

if err := lob.get("letters/"+id, nil, resp); err != nil {
return resp, err
}

return resp, nil
}

// ListLetters retrieves information on all letters we've ever made, in reverse chrono order
func (lob *lob) ListLetters(count int) (*ListLettersResponse, error) {
if count <= 0 {
count = 10
}

query := map[string]string{
"limit": strconv.Itoa(count),
}

resp := new(ListLettersResponse)
if err := lob.get("letters", query, resp); err != nil {
return nil, err
}

return resp, nil
}

// CancelLetter prevents a letter from being sent, if the send date has yet to pass
func (lob *lob) CancelLetter(id string) (*CancelLetterResponse, error) {
resp := new(CancelLetterResponse)
if err := lob.delete("letters/"+id, &resp); err != nil {
return resp, err
}

return resp, nil
}
Loading