forked from go-packages/paypal
* feature/Invoice: init, added Invoice related structs, implemented methods:GenerateInvoiceNumber,GetInvoiceDetails(by ID) * feature/Invoice: added implemented endpoints to README along with Usage Co-authored-by: Akshay Prabhakant <akshayprabhakant@Akshays-MacBook-Pro.local>
This commit is contained in:
parent
1bb626d559
commit
4c16ffad0a
26
README.md
26
README.md
|
@ -102,6 +102,11 @@
|
||||||
* POST /v1/billing/subscriptions/:id/suspend
|
* POST /v1/billing/subscriptions/:id/suspend
|
||||||
* GET /v1/billing/subscriptions/:id/transactions
|
* GET /v1/billing/subscriptions/:id/transactions
|
||||||
|
|
||||||
|
### Invoicing
|
||||||
|
|
||||||
|
* POST /v2/invoicing/generate-next-invoice-number
|
||||||
|
* GET /v2/invoicing/invoices/:id
|
||||||
|
|
||||||
## Missing endpoints
|
## Missing endpoints
|
||||||
|
|
||||||
It is possible that some endpoints are missing in this Client, but you can use built-in `paypal` functions to perform a request: `NewClient -> NewRequest -> SendWithAuth`
|
It is possible that some endpoints are missing in this Client, but you can use built-in `paypal` functions to perform a request: `NewClient -> NewRequest -> SendWithAuth`
|
||||||
|
@ -371,6 +376,27 @@ c.DeleteWebhook("WebhookID")
|
||||||
c.ListWebhooks(paypal.AncorTypeApplication)
|
c.ListWebhooks(paypal.AncorTypeApplication)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Generate Next Invoice Number
|
||||||
|
```go
|
||||||
|
// GenerateInvoiceNumber: generates the next invoice number that is available to the merchant.
|
||||||
|
c.GenerateInvoiceNumber(ctx) // might return something like "0001" or "0010".
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Invoice Details by ID
|
||||||
|
```go
|
||||||
|
// the second argument is an ID, it should be valid
|
||||||
|
invoice, err := c.GetInvoiceDetails(ctx, "INV2-XFXV-YW42-ZANU-4F33")
|
||||||
|
```
|
||||||
|
* for now, we are yet to implement the ShowAllInvoices endpoint, so use the following cURL request for the same(this gives you the list of invoice-IDs for this customer)
|
||||||
|
```bash
|
||||||
|
curl -v -X GET https://api-m.sandbox.paypal.com/v2/invoicing/invoices?total_required=true \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer <Token>"
|
||||||
|
```
|
||||||
|
|
||||||
|
* refer to the beginning of this Usage section for obtaining a Token.
|
||||||
|
|
||||||
|
|
||||||
## How to Contribute
|
## How to Contribute
|
||||||
|
|
||||||
* Fork a repository
|
* Fork a repository
|
||||||
|
|
38
invoicing.go
Normal file
38
invoicing.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package paypal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateInvoiceNumber: generates the next invoice number that is available to the merchant.
|
||||||
|
// Endpoint: POST /v2/invoicing/generate-next-invoice-number
|
||||||
|
func (c *Client) GenerateInvoiceNumber(ctx context.Context) (*InvoiceNumber, error) {
|
||||||
|
|
||||||
|
req, err := c.NewRequest(ctx, "POST", fmt.Sprintf("%s%s", c.APIBase, "/v2/invoicing/generate-next-invoice-number"), nil)
|
||||||
|
nextInvoiceNumber := &InvoiceNumber{}
|
||||||
|
if err != nil {
|
||||||
|
return nextInvoiceNumber, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.SendWithAuth(req, nextInvoiceNumber); err != nil {
|
||||||
|
return nextInvoiceNumber, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextInvoiceNumber, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInvoiceDetails: show invoice details for a particular invoice by ID.
|
||||||
|
// Endpoint: GET /v2/invoicing/invoices/{invoice_id}
|
||||||
|
func (c *Client) GetInvoiceDetails(ctx context.Context, invoiceID string) (*Invoice, error) {
|
||||||
|
req, err := c.NewRequest(ctx, "GET", fmt.Sprintf("%s%s%s", c.APIBase, "/v2/invoicing/invoices/", invoiceID), nil)
|
||||||
|
invoice := &Invoice{}
|
||||||
|
if err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.SendWithAuth(req, invoice); err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
return invoice, nil
|
||||||
|
}
|
406
invoicing_test.go
Normal file
406
invoicing_test.go
Normal file
|
@ -0,0 +1,406 @@
|
||||||
|
package paypal_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/plutov/paypal/v4"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// All test values are defined here
|
||||||
|
var devTestClientID = "AXy9orp-CDaHhBZ9C78QHW2BKZpACgroqo85_NIOa9mIfJ9QnSVKzY-X_rivR_fTUUr6aLjcJsj6sDur"
|
||||||
|
var devTestSecret = "EBoIiUSkCKeSk49hHSgTem1qnjzzJgRQHDEHvGpzlLEf_nIoJd91xu8rPOBDCdR_UYNKVxJE-UgS2iCw"
|
||||||
|
var devAPIBaseSandBox = "https://api.sandbox.paypal.com"
|
||||||
|
|
||||||
|
func TestGenerateInvoiceNumber(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
c, _ := paypal.NewClient(devTestClientID, devTestSecret, devAPIBaseSandBox)
|
||||||
|
_, err := c.GetAccessToken(ctx)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
_, err = c.GenerateInvoiceNumber(ctx)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
}
|
||||||
|
func assertTwoInvoices(t *testing.T, invoice paypal.Invoice, testInvoice paypal.Invoice) {
|
||||||
|
|
||||||
|
// additional_recipients
|
||||||
|
assert.Equal(t, len(invoice.AdditionalRecipients), len(testInvoice.AdditionalRecipients))
|
||||||
|
// additional_recipients --> email_address !! EQUALITY OF SPLICE OF STRUCT REMAINING !!
|
||||||
|
|
||||||
|
// amount
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Currency, testInvoice.AmountSummary.Currency)
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Value, testInvoice.AmountSummary.Value)
|
||||||
|
// amount-->breakdown-->custom-->amount
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Custom.Amount.Currency, testInvoice.AmountSummary.Breakdown.Custom.Amount.Currency)
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Custom.Amount.Value, testInvoice.AmountSummary.Breakdown.Custom.Amount.Value)
|
||||||
|
// amount-->breakdown-->custom-->label
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Custom.Label, testInvoice.AmountSummary.Breakdown.Custom.Label)
|
||||||
|
// amount-->breakdown-->discount-->amount
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Discount.InvoiceDiscount.DiscountAmount.Currency, testInvoice.AmountSummary.Breakdown.Discount.InvoiceDiscount.DiscountAmount.Currency)
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Discount.InvoiceDiscount.DiscountAmount.Value, testInvoice.AmountSummary.Breakdown.Discount.InvoiceDiscount.DiscountAmount.Value)
|
||||||
|
// amount-->breakdown-->discount-->percent
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Discount.InvoiceDiscount.Percent, testInvoice.AmountSummary.Breakdown.Discount.InvoiceDiscount.Percent)
|
||||||
|
// amount-->breakdown-->discount-->item_discount
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Discount.ItemDiscount.Currency, testInvoice.AmountSummary.Breakdown.Discount.ItemDiscount.Currency)
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Discount.ItemDiscount.Value, testInvoice.AmountSummary.Breakdown.Discount.ItemDiscount.Value)
|
||||||
|
// amount-->breakdown-->item_total
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.ItemTotal.Currency, testInvoice.AmountSummary.Breakdown.ItemTotal.Currency)
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.ItemTotal.Value, testInvoice.AmountSummary.Breakdown.ItemTotal.Value)
|
||||||
|
// amount-->breakdown-->shipping-->amount
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Shipping.Amount.Currency, testInvoice.AmountSummary.Breakdown.Shipping.Amount.Currency)
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Shipping.Amount.Value, testInvoice.AmountSummary.Breakdown.Shipping.Amount.Value)
|
||||||
|
// amount-->breakdown-->shipping-->tax
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Shipping.Tax.Amount.Currency, testInvoice.AmountSummary.Breakdown.Shipping.Tax.Amount.Currency)
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Shipping.Tax.Amount.Value, testInvoice.AmountSummary.Breakdown.Shipping.Tax.Amount.Value)
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Shipping.Tax.ID, testInvoice.AmountSummary.Breakdown.Shipping.Tax.ID)
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Shipping.Tax.Name, testInvoice.AmountSummary.Breakdown.Shipping.Tax.Name)
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.Shipping.Tax.Percent, testInvoice.AmountSummary.Breakdown.Shipping.Tax.Percent)
|
||||||
|
// amount-->breakdown-->tax_total
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.TaxTotal.Currency, testInvoice.AmountSummary.Breakdown.TaxTotal.Currency)
|
||||||
|
assert.Equal(t, invoice.AmountSummary.Breakdown.TaxTotal.Value, testInvoice.AmountSummary.Breakdown.TaxTotal.Value)
|
||||||
|
|
||||||
|
// configuration
|
||||||
|
assert.Equal(t, invoice.Configuration.AllowTip, testInvoice.Configuration.AllowTip)
|
||||||
|
assert.Equal(t, invoice.Configuration.TaxCalculatedAfterDiscount, testInvoice.Configuration.TaxCalculatedAfterDiscount)
|
||||||
|
assert.Equal(t, invoice.Configuration.TaxInclusive, testInvoice.Configuration.TaxInclusive)
|
||||||
|
assert.Equal(t, invoice.Configuration.TemplateId, testInvoice.Configuration.TemplateId)
|
||||||
|
// configuration --> partial_payment
|
||||||
|
assert.Equal(t, invoice.Configuration.PartialPayment.AllowPartialPayment, testInvoice.Configuration.PartialPayment.AllowPartialPayment)
|
||||||
|
assert.Equal(t, invoice.Configuration.PartialPayment.MinimumAmountDue.Currency, testInvoice.Configuration.PartialPayment.MinimumAmountDue.Currency)
|
||||||
|
assert.Equal(t, invoice.Configuration.PartialPayment.MinimumAmountDue.Value, testInvoice.Configuration.PartialPayment.MinimumAmountDue.Value)
|
||||||
|
|
||||||
|
// detail
|
||||||
|
assert.Equal(t, invoice.Detail.CurrencyCode, testInvoice.Detail.CurrencyCode)
|
||||||
|
assert.Equal(t, invoice.Detail.InvoiceDate, testInvoice.Detail.InvoiceDate)
|
||||||
|
assert.Equal(t, invoice.Detail.InvoiceNumber, testInvoice.Detail.InvoiceNumber)
|
||||||
|
assert.Equal(t, invoice.Detail.Memo, testInvoice.Detail.Memo)
|
||||||
|
assert.Equal(t, invoice.Detail.Note, testInvoice.Detail.Note)
|
||||||
|
assert.Equal(t, invoice.Detail.Reference, testInvoice.Detail.Reference)
|
||||||
|
assert.Equal(t, invoice.Detail.TermsAndConditions, testInvoice.Detail.TermsAndConditions)
|
||||||
|
// detail --> attachments !! EQUALITY OF SPLICE OF STRUCT REMAINING !!
|
||||||
|
assert.Equal(t, len(invoice.Detail.Attachments), len(testInvoice.Detail.Attachments))
|
||||||
|
// detail --> metadata
|
||||||
|
assert.Equal(t, invoice.Detail.Metadata.CancelTime, testInvoice.Detail.Metadata.CancelTime)
|
||||||
|
assert.Equal(t, invoice.Detail.Metadata.CancellledTimeBy, testInvoice.Detail.Metadata.CancellledTimeBy)
|
||||||
|
assert.Equal(t, invoice.Detail.Metadata.CreateTime, testInvoice.Detail.Metadata.CreateTime)
|
||||||
|
assert.Equal(t, invoice.Detail.Metadata.CreatedBy, testInvoice.Detail.Metadata.CreatedBy)
|
||||||
|
assert.Equal(t, invoice.Detail.Metadata.CreatedByFlow, testInvoice.Detail.Metadata.CreatedByFlow)
|
||||||
|
assert.Equal(t, invoice.Detail.Metadata.FirstSentTime, testInvoice.Detail.Metadata.FirstSentTime)
|
||||||
|
assert.Equal(t, invoice.Detail.Metadata.InvoicerViewUrl, testInvoice.Detail.Metadata.InvoicerViewUrl)
|
||||||
|
assert.Equal(t, invoice.Detail.Metadata.LastSentBy, testInvoice.Detail.Metadata.LastSentBy)
|
||||||
|
assert.Equal(t, invoice.Detail.Metadata.LastSentTime, testInvoice.Detail.Metadata.LastSentTime)
|
||||||
|
assert.Equal(t, invoice.Detail.Metadata.LastUpdateTime, testInvoice.Detail.Metadata.LastUpdateTime)
|
||||||
|
assert.Equal(t, invoice.Detail.Metadata.LastUpdatedBy, testInvoice.Detail.Metadata.LastUpdatedBy)
|
||||||
|
assert.Equal(t, invoice.Detail.Metadata.RecipientViewUrl, testInvoice.Detail.Metadata.RecipientViewUrl)
|
||||||
|
// detail --> payment_term
|
||||||
|
assert.Equal(t, invoice.Detail.PaymentTerm.DueDate, testInvoice.Detail.PaymentTerm.DueDate)
|
||||||
|
assert.Equal(t, invoice.Detail.PaymentTerm.TermType, testInvoice.Detail.PaymentTerm.TermType)
|
||||||
|
|
||||||
|
// due_amount
|
||||||
|
assert.Equal(t, invoice.DueAmount.Currency, testInvoice.DueAmount.Currency)
|
||||||
|
assert.Equal(t, invoice.DueAmount.Value, testInvoice.DueAmount.Value)
|
||||||
|
|
||||||
|
// gratuity
|
||||||
|
assert.Equal(t, invoice.Gratuity.Currency, testInvoice.Gratuity.Currency)
|
||||||
|
assert.Equal(t, invoice.Gratuity.Value, testInvoice.Gratuity.Value)
|
||||||
|
|
||||||
|
// id
|
||||||
|
assert.Equal(t, invoice.ID, testInvoice.ID)
|
||||||
|
|
||||||
|
// invoicer
|
||||||
|
assert.Equal(t, invoice.Invoicer.AdditionalNotes, testInvoice.Invoicer.AdditionalNotes)
|
||||||
|
assert.Equal(t, invoice.Invoicer.EmailAddress, testInvoice.Invoicer.EmailAddress)
|
||||||
|
assert.Equal(t, invoice.Invoicer.LogoUrl, testInvoice.Invoicer.LogoUrl)
|
||||||
|
assert.Equal(t, invoice.Invoicer.TaxId, testInvoice.Invoicer.TaxId)
|
||||||
|
assert.Equal(t, invoice.Invoicer.Website, testInvoice.Invoicer.Website)
|
||||||
|
// !!! SPLICE EQUALITY STILL REMAINING !!!!!
|
||||||
|
// invoicer --> phones
|
||||||
|
assert.Equal(t, len(invoice.Invoicer.Phones), len(testInvoice.Invoicer.Phones))
|
||||||
|
|
||||||
|
// items
|
||||||
|
// !!! SPLICE EQUALITY STILL REMAINING !!!!!
|
||||||
|
assert.Equal(t, len(invoice.Items), len(testInvoice.Items))
|
||||||
|
|
||||||
|
// links
|
||||||
|
// !!! SPLICE EQUALITY STILL REMAINING !!!!!
|
||||||
|
assert.Equal(t, len(invoice.Links), len(testInvoice.Links))
|
||||||
|
|
||||||
|
// parent_id
|
||||||
|
assert.Equal(t, invoice.ParentID, testInvoice.ParentID)
|
||||||
|
|
||||||
|
// payments
|
||||||
|
assert.Equal(t, invoice.Payments.PaidAmount.Currency, testInvoice.Payments.PaidAmount.Currency)
|
||||||
|
assert.Equal(t, invoice.Payments.PaidAmount.Value, testInvoice.Payments.PaidAmount.Value)
|
||||||
|
// payments --> transactions
|
||||||
|
assert.Equal(t, len(invoice.Payments.Transactions), len(testInvoice.Payments.Transactions))
|
||||||
|
|
||||||
|
// primary_recipients
|
||||||
|
// !!! SPLICE EQUALITY STILL REMAINING !!!!!
|
||||||
|
assert.Equal(t, len(invoice.PrimaryRecipients), len(testInvoice.PrimaryRecipients))
|
||||||
|
|
||||||
|
// refunds
|
||||||
|
assert.Equal(t, invoice.Refunds.RefundAmount.Currency, testInvoice.Refunds.RefundAmount.Currency)
|
||||||
|
assert.Equal(t, invoice.Refunds.RefundAmount.Value, testInvoice.Refunds.RefundAmount.Value)
|
||||||
|
assert.Equal(t, len(invoice.Refunds.RefundDetails), len(testInvoice.Refunds.RefundDetails))
|
||||||
|
|
||||||
|
// status
|
||||||
|
assert.Equal(t, invoice.Status, testInvoice.Status)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetInvoice(t *testing.T) {
|
||||||
|
testInvoiceJSONData := []byte(`
|
||||||
|
{
|
||||||
|
"amount": {
|
||||||
|
"breakdown": {
|
||||||
|
"custom": {
|
||||||
|
"amount": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "10.00"
|
||||||
|
},
|
||||||
|
"label": "Packing Charges"
|
||||||
|
},
|
||||||
|
"discount": {
|
||||||
|
"invoice_discount": {
|
||||||
|
"amount": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "-2.63"
|
||||||
|
},
|
||||||
|
"percent": "5"
|
||||||
|
},
|
||||||
|
"item_discount": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "-7.50"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"item_total": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "60.00"
|
||||||
|
},
|
||||||
|
"shipping": {
|
||||||
|
"amount": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "10.00"
|
||||||
|
},
|
||||||
|
"tax": {
|
||||||
|
"amount": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "0.73"
|
||||||
|
},
|
||||||
|
"id": "TAX-9AU06895VD287170A",
|
||||||
|
"name": "Sales Tax",
|
||||||
|
"percent": "7.25"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tax_total": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "4.34"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "74.21"
|
||||||
|
},
|
||||||
|
"configuration": {
|
||||||
|
"allow_tip": true,
|
||||||
|
"partial_payment": {
|
||||||
|
"allow_partial_payment": true,
|
||||||
|
"minimum_amount_due": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "20.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tax_calculated_after_discount": true,
|
||||||
|
"tax_inclusive": false,
|
||||||
|
"template_id": "TEMP-4NW98229SC0703920"
|
||||||
|
},
|
||||||
|
"detail": {
|
||||||
|
"additional_data": "2-4",
|
||||||
|
"archived": false,
|
||||||
|
"category_code": "SHIPPABLE",
|
||||||
|
"currency_code": "USD",
|
||||||
|
"group_draft": false,
|
||||||
|
"invoice_date": "2018-11-12",
|
||||||
|
"invoice_number": "0001",
|
||||||
|
"memo": "This is a long contract",
|
||||||
|
"metadata": {
|
||||||
|
"caller_type": "API_V2_INVOICE",
|
||||||
|
"create_time": "2022-10-25T16:54:50Z",
|
||||||
|
"created_by_flow": "REGULAR_SINGLE",
|
||||||
|
"invoicer_view_url": "https://www.sandbox.paypal.com/invoice/details/INV2-XFXV-YW42-ZANU-4F33",
|
||||||
|
"last_update_time": "2022-10-25T16:54:50Z",
|
||||||
|
"recipient_view_url": "https://www.sandbox.paypal.com/invoice/p/#XFXVYW42ZANU4F33"
|
||||||
|
},
|
||||||
|
"note": "Thank you for your business.",
|
||||||
|
"payment_term": {
|
||||||
|
"due_date": "2018-11-22",
|
||||||
|
"term_type": "NET_10"
|
||||||
|
},
|
||||||
|
"reference": "deal-ref",
|
||||||
|
"viewed_by_recipient": false
|
||||||
|
},
|
||||||
|
"due_amount": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "74.21"
|
||||||
|
},
|
||||||
|
"id": "INV2-XFXV-YW42-ZANU-4F33",
|
||||||
|
"invoicer": {
|
||||||
|
"additional_notes": "2-4",
|
||||||
|
"address": {
|
||||||
|
"address_line_1": "1234 First Street",
|
||||||
|
"address_line_2": "337673 Hillside Court",
|
||||||
|
"admin_area_1": "CA",
|
||||||
|
"admin_area_2": "Anytown",
|
||||||
|
"country_code": "US",
|
||||||
|
"postal_code": "98765"
|
||||||
|
},
|
||||||
|
"email_address": "merchant@example.com",
|
||||||
|
"logo_url": "https://example.com/logo.PNG",
|
||||||
|
"name": {
|
||||||
|
"full_name": "David Larusso",
|
||||||
|
"given_name": "David",
|
||||||
|
"surname": "Larusso"
|
||||||
|
},
|
||||||
|
"phones": [
|
||||||
|
{
|
||||||
|
"country_code": "001",
|
||||||
|
"national_number": "4085551234",
|
||||||
|
"phone_type": "MOBILE"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tax_id": "ABcNkWSfb5ICTt73nD3QON1fnnpgNKBy- Jb5SeuGj185MNNw6g",
|
||||||
|
"website": "www.test.com"
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"description": "Elastic mat to practice yoga.",
|
||||||
|
"discount": {
|
||||||
|
"amount": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "-2.50"
|
||||||
|
},
|
||||||
|
"percent": "5"
|
||||||
|
},
|
||||||
|
"id": "ITEM-5335764681676603X",
|
||||||
|
"name": "Yoga Mat",
|
||||||
|
"quantity": "1",
|
||||||
|
"tax": {
|
||||||
|
"amount": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "3.27"
|
||||||
|
},
|
||||||
|
"id": "TAX-5XV24702TP4910056",
|
||||||
|
"name": "Sales Tax",
|
||||||
|
"percent": "7.25"
|
||||||
|
},
|
||||||
|
"unit_amount": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "50.00"
|
||||||
|
},
|
||||||
|
"unit_of_measure": "QUANTITY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"discount": {
|
||||||
|
"amount": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "-5.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "ITEM-1B467958Y9218273X",
|
||||||
|
"name": "Yoga t-shirt",
|
||||||
|
"quantity": "1",
|
||||||
|
"tax": {
|
||||||
|
"amount": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "0.34"
|
||||||
|
},
|
||||||
|
"id": "TAX-5XV24702TP4910056",
|
||||||
|
"name": "Sales Tax",
|
||||||
|
"percent": "7.25"
|
||||||
|
},
|
||||||
|
"unit_amount": {
|
||||||
|
"currency_code": "USD",
|
||||||
|
"value": "10.00"
|
||||||
|
},
|
||||||
|
"unit_of_measure": "QUANTITY"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "https://api.sandbox.paypal.com/v2/invoicing/invoices/INV2-XFXV-YW42-ZANU-4F33",
|
||||||
|
"method": "GET",
|
||||||
|
"rel": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "https://api.sandbox.paypal.com/v2/invoicing/invoices/INV2-XFXV-YW42-ZANU-4F33/send",
|
||||||
|
"method": "POST",
|
||||||
|
"rel": "send"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "https://api.sandbox.paypal.com/v2/invoicing/invoices/INV2-XFXV-YW42-ZANU-4F33",
|
||||||
|
"method": "PUT",
|
||||||
|
"rel": "replace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "https://api.sandbox.paypal.com/v2/invoicing/invoices/INV2-XFXV-YW42-ZANU-4F33",
|
||||||
|
"method": "DELETE",
|
||||||
|
"rel": "delete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "https://api.sandbox.paypal.com/v2/invoicing/invoices/INV2-XFXV-YW42-ZANU-4F33/payments",
|
||||||
|
"method": "POST",
|
||||||
|
"rel": "record-payment"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primary_recipients": [
|
||||||
|
{
|
||||||
|
"billing_info": {
|
||||||
|
"address": {
|
||||||
|
"address_line_1": "1234 Main Street",
|
||||||
|
"admin_area_1": "CA",
|
||||||
|
"admin_area_2": "Anytown",
|
||||||
|
"country_code": "US",
|
||||||
|
"postal_code": "98765"
|
||||||
|
},
|
||||||
|
"email_address": "bill-me@example.com",
|
||||||
|
"name": {
|
||||||
|
"full_name": "Stephanie Meyers",
|
||||||
|
"given_name": "Stephanie",
|
||||||
|
"surname": "Meyers"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shipping_info": {
|
||||||
|
"address": {
|
||||||
|
"address_line_1": "1234 Main Street",
|
||||||
|
"admin_area_1": "CA",
|
||||||
|
"admin_area_2": "Anytown",
|
||||||
|
"country_code": "US",
|
||||||
|
"postal_code": "98765"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"full_name": "Stephanie Meyers",
|
||||||
|
"given_name": "Stephanie",
|
||||||
|
"surname": "Meyers"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": "DRAFT",
|
||||||
|
"unilateral": false
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
var testInvoice paypal.Invoice
|
||||||
|
err := json.Unmarshal(testInvoiceJSONData, &testInvoice)
|
||||||
|
assert.Equal(t, nil, err) // if passed, means unmarshalling was successful
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
c, _ := paypal.NewClient(devTestClientID, devTestSecret, devAPIBaseSandBox)
|
||||||
|
_, _ = c.GetAccessToken(ctx)
|
||||||
|
|
||||||
|
invoice, err := c.GetInvoiceDetails(ctx, "INV2-XFXV-YW42-ZANU-4F33")
|
||||||
|
assert.Equal(t, nil, err) // if passed, means that request was successful
|
||||||
|
assertTwoInvoices(t, *invoice, testInvoice)
|
||||||
|
}
|
258
types.go
258
types.go
|
@ -204,6 +204,264 @@ type (
|
||||||
CancelURL string `json:"cancel_url,omitempty"`
|
CancelURL string `json:"cancel_url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoicing relates structures
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#invoices_generate-next-invoice-number
|
||||||
|
InvoiceNumber struct {
|
||||||
|
InvoiceNumberValue string `json:"invoice_number"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in InvoiceAmountWithBreakdown
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-custom_amount
|
||||||
|
CustomAmount struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Amount Money `json:"amount,omitempty"`
|
||||||
|
}
|
||||||
|
// Used in AggregatedDiscount
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-discount
|
||||||
|
InvoicingDiscount struct {
|
||||||
|
DiscountAmount Money `json:"amount,omitempty"`
|
||||||
|
Percent string `json:"percent,omitempty"`
|
||||||
|
}
|
||||||
|
// Used in InvoiceAmountWithBreakdown
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-aggregated_discount
|
||||||
|
AggregatedDiscount struct {
|
||||||
|
InvoiceDiscount InvoicingDiscount `json:"invoice_discount,omitempty"`
|
||||||
|
ItemDiscount *Money `json:"item_discount,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-tax
|
||||||
|
InvoiceTax struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Percent string `json:"percent,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"` // not mentioned here, but is still returned in response payload, when invoice is requested by ID.
|
||||||
|
Amount Money `json:"amount,omitempty"`
|
||||||
|
}
|
||||||
|
// Used in InvoiceAmountWithBreakdown struct
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-shipping_cost
|
||||||
|
InvoiceShippingCost struct {
|
||||||
|
Amount Money `json:"amount,omitempty"`
|
||||||
|
Tax InvoiceTax `json:"tax,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used in AmountSummaryDetail
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/payments/v2/#definition-nrp-nrr_attributes
|
||||||
|
InvoiceAmountWithBreakdown struct {
|
||||||
|
Custom CustomAmount `json:"custom,omitempty"` // The custom amount to apply to an invoice.
|
||||||
|
Discount AggregatedDiscount `json:"discount,omitempty"`
|
||||||
|
ItemTotal Money `json:"item_total,omitempty"` // The subtotal for all items.
|
||||||
|
Shipping InvoiceShippingCost `json:"shipping,omitempty"` // The shipping fee for all items. Includes tax on shipping.
|
||||||
|
TaxTotal Money `json:"tax_total,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoice AmountSummary
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-amount_summary_detail
|
||||||
|
AmountSummaryDetail struct {
|
||||||
|
Breakdown InvoiceAmountWithBreakdown `json:"breakdown,omitempty"`
|
||||||
|
Currency string `json:"currency_code,omitempty"`
|
||||||
|
Value string `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-partial_payment
|
||||||
|
InvoicePartialPayment struct {
|
||||||
|
AllowPartialPayment bool `json:"allow_partial_payment,omitempty"`
|
||||||
|
MinimumAmountDue Money `json:"minimum_amount_due,omitempty"` // Valid only when allow_partial_payment is true.
|
||||||
|
}
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-configuration
|
||||||
|
InvoiceConfiguration struct {
|
||||||
|
AllowTip bool `json:"allow_tip,omitempty"`
|
||||||
|
PartialPayment InvoicePartialPayment `json:"partial_payment,omitempty"`
|
||||||
|
TaxCalculatedAfterDiscount bool `json:"tax_calculated_after_discount,omitempty"`
|
||||||
|
TaxInclusive bool `json:"tax_inclusive,omitempty"`
|
||||||
|
TemplateId string `json:"template_id,omitempty"`
|
||||||
|
}
|
||||||
|
// used in InvoiceDetail structure
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-file_reference
|
||||||
|
InvoiceFileReference struct {
|
||||||
|
ContentType string `json:"content_type,omitempty"`
|
||||||
|
CreateTime string `json:"create_time,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
URL string `json:"reference_url,omitempty"`
|
||||||
|
Size string `json:"size,omitempty"`
|
||||||
|
}
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-metadata
|
||||||
|
InvoiceAuditMetadata struct {
|
||||||
|
CreateTime string `json:"create_time,omitempty"`
|
||||||
|
CreatedBy string `json:"created_by,omitempty"`
|
||||||
|
LastUpdateTime string `json:"last_update_time,omitempty"`
|
||||||
|
LastUpdatedBy string `json:"last_updated_by,omitempty"`
|
||||||
|
CancelTime string `json:"cancel_time,omitempty"`
|
||||||
|
CancellledTimeBy string `json:"cancelled_by,omitempty"`
|
||||||
|
CreatedByFlow string `json:"created_by_flow,omitempty"`
|
||||||
|
FirstSentTime string `json:"first_sent_time,omitempty"`
|
||||||
|
InvoicerViewUrl string `json:"invoicer_view_url,omitempty"`
|
||||||
|
LastSentBy string `json:"last_sent_by,omitempty"`
|
||||||
|
LastSentTime string `json:"last_sent_time,omitempty"`
|
||||||
|
RecipientViewUrl string `json:"recipient_view_url,omitempty"`
|
||||||
|
}
|
||||||
|
// used in InvoiceDetail struct
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-invoice_payment_term
|
||||||
|
InvoicePaymentTerm struct {
|
||||||
|
TermType string `json:"term_type,omitempty"`
|
||||||
|
DueDate string `json:"due_date,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in Invoice struct
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-invoice_detail
|
||||||
|
InvoiceDetail struct {
|
||||||
|
CurrencyCode string `json:"currency_code"` // required, hence omitempty not used
|
||||||
|
Attachments []InvoiceFileReference `json:"attachments,omitempty"`
|
||||||
|
Memo string `json:"memo,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
Reference string `json:"reference,omitempty"`
|
||||||
|
TermsAndConditions string `json:"terms_and_conditions,omitempty"`
|
||||||
|
InvoiceDate string `json:"invoice_date,omitempty"`
|
||||||
|
InvoiceNumber string `json:"invoice_number,omitempty"`
|
||||||
|
Metadata InvoiceAuditMetadata `json:"metadata,omitempty"` // The audit metadata.
|
||||||
|
PaymentTerm InvoicePaymentTerm `json:"payment_term,omitempty"` // payment due date for the invoice. Value is either but not both term_type or due_date.
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in InvoicerInfo struct
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-phone_detail
|
||||||
|
InvoicerPhoneDetail struct {
|
||||||
|
CountryCode string `json:"country_code"`
|
||||||
|
NationalNumber string `json:"national_number"`
|
||||||
|
ExtensionNumber string `json:"extension_number,omitempty"`
|
||||||
|
PhoneType string `json:"phone_type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in Invoice struct
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-invoicer_info
|
||||||
|
InvoicerInfo struct {
|
||||||
|
AdditionalNotes string `json:"additional_notes,omitempty"`
|
||||||
|
EmailAddress string `json:"email_address,omitempty"`
|
||||||
|
LogoUrl string `json:"logo_url,omitempty"`
|
||||||
|
Phones []InvoicerPhoneDetail `json:"phones,omitempty"`
|
||||||
|
TaxId string `json:"tax_id,omitempty"`
|
||||||
|
Website string `json:"website,omitempty"`
|
||||||
|
}
|
||||||
|
// Used in Invoice struct
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-item
|
||||||
|
InvoiceItem struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Quantity string `json:"quantity"`
|
||||||
|
UnitAmount Money `json:"unit_amount"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
InvoiceDiscount InvoicingDiscount `json:"discount,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
ItemDate string `json:"item_date,omitempty"`
|
||||||
|
Tax InvoiceTax `json:"tax,omitempty"`
|
||||||
|
UnitOfMeasure string `json:"unit_of_measure,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in InvoiceAddressPortable
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-address_details
|
||||||
|
InvoiceAddressDetails struct {
|
||||||
|
BuildingName string `json:"building_name,omitempty"`
|
||||||
|
DeliveryService string `json:"delivery_service,omitempty"`
|
||||||
|
StreetName string `json:"street_name,omitempty"`
|
||||||
|
StreetNumber string `json:"street_number,omitempty"`
|
||||||
|
StreetType string `json:"street_type,omitempty"`
|
||||||
|
SubBuilding string `json:"sub_building,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in InvoiceContactInfo
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-address_portable
|
||||||
|
InvoiceAddressPortable struct {
|
||||||
|
CountryCode string `json:"country_code"`
|
||||||
|
AddressDetails InvoiceAddressDetails `json:"address_details,omitempty"`
|
||||||
|
AddressLine1 string `json:"address_line_1,omitempty"`
|
||||||
|
AddressLine2 string `json:"address_line_2,omitempty"`
|
||||||
|
AddressLine3 string `json:"address_line_3,omitempty"`
|
||||||
|
AdminArea1 string `json:"admin_area_1,omitempty"`
|
||||||
|
AdminArea2 string `json:"admin_area_2,omitempty"`
|
||||||
|
AdminArea3 string `json:"admin_area_3,omitempty"`
|
||||||
|
AdminArea4 string `json:"admin_area_4,omitempty"`
|
||||||
|
PostalCode string `json:"postal_code,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in InvoicePaymentDetails
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-contact_information
|
||||||
|
InvoiceContactInfo struct {
|
||||||
|
BusinessName string `json:"business_name,omitempty"`
|
||||||
|
RecipientAddress InvoiceAddressPortable `json:"address,omitempty"` // address of the recipient.
|
||||||
|
RecipientName Name `json:"name,omitempty"` // The first and Last name of the recipient.
|
||||||
|
}
|
||||||
|
//used in InvoicePayments struct
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-payment_detail
|
||||||
|
InvoicePaymentDetails struct {
|
||||||
|
Method string `json:"method"`
|
||||||
|
Amount Money `json:"amount,omitempty"`
|
||||||
|
Note string `json:"note,omitempty"`
|
||||||
|
PaymentDate string `json:"payment_date,omitempty"`
|
||||||
|
PaymentID string `json:"payment_id,omitempty"`
|
||||||
|
ShippingInfo InvoiceContactInfo `json:"shipping_info,omitempty"` // The recipient's shipping information.
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in Invoice
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-payments
|
||||||
|
InvoicePayments struct {
|
||||||
|
PaidAmount Money `json:"paid_amount,omitempty"`
|
||||||
|
Transactions []InvoicePaymentDetails `json:"transactions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in InvoiceRecipientInfo
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-billing_info
|
||||||
|
InvoiceBillingInfo struct {
|
||||||
|
AdditionalInfo string `json:"additional_info,omitempty"`
|
||||||
|
EmailAddress string `json:"email_address,omitempty"`
|
||||||
|
Language string `json:"language,omitempty"`
|
||||||
|
Phones []InvoicerPhoneDetail `json:"phones,omitempty"` // invoice recipient's phone numbers.
|
||||||
|
}
|
||||||
|
// used in Invoice struct
|
||||||
|
// Doc:
|
||||||
|
InvoiceRecipientInfo struct {
|
||||||
|
BillingInfo InvoiceBillingInfo `json:"billing_info,omitempty"` // billing information for the invoice recipient.
|
||||||
|
ShippingInfo InvoiceContactInfo `json:"shipping_info,omitempty"` // recipient's shipping information.
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in InvoiceRefund struct
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-refund_detail
|
||||||
|
InvoiceRefundDetails struct {
|
||||||
|
Method string `json:"method"`
|
||||||
|
RefundAmount Money `json:"amount,omitempty"`
|
||||||
|
RefundDate string `json:"refund_date,omitempty"`
|
||||||
|
RefundID string `json:"refund_id,omitempty"`
|
||||||
|
RefundType string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in Invoice struct
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-refunds
|
||||||
|
InvoiceRefund struct {
|
||||||
|
RefundAmount Money `json:"refund_amount,omitempty"`
|
||||||
|
RefundDetails []InvoiceRefundDetails `json:"transactions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in Invoice struct
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#definition-email_address
|
||||||
|
InvoiceEmailAddress struct {
|
||||||
|
EmailAddress string `json:"email_address,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// to contain Invoice related fields
|
||||||
|
// Doc: https://developer.paypal.com/docs/api/invoicing/v2/#invoices_get
|
||||||
|
Invoice struct {
|
||||||
|
AdditionalRecipients []InvoiceEmailAddress `json:"additional_recipients,omitempty"` // An array of one or more CC: emails to which notifications are sent.
|
||||||
|
AmountSummary AmountSummaryDetail `json:"amount,omitempty"`
|
||||||
|
Configuration InvoiceConfiguration `json:"configuration,omitempty"`
|
||||||
|
Detail InvoiceDetail `json:"detail,omitempty"`
|
||||||
|
DueAmount Money `json:"due_amount,omitempty"` // balance amount outstanding after payments.
|
||||||
|
Gratuity Money `json:"gratuity,omitempty"` // amount paid by the payer as gratuity to the invoicer.
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Invoicer InvoicerInfo `json:"invoicer,omitempty"`
|
||||||
|
Items []InvoiceItem `json:"items,omitempty"`
|
||||||
|
Links []Link `json:"links,omitempty"`
|
||||||
|
ParentID string `json:"parent_id,omitempty"`
|
||||||
|
Payments InvoicePayments `json:"payments,omitempty"`
|
||||||
|
PrimaryRecipients []InvoiceRecipientInfo `json:"primary_recipients,omitempty"`
|
||||||
|
Refunds InvoiceRefund `json:"refunds,omitempty"` // List of refunds against this invoice.
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Doc: https://developer.paypal.com/api/orders/v2/#definition-payment_method
|
// Doc: https://developer.paypal.com/api/orders/v2/#definition-payment_method
|
||||||
PaymentMethod struct {
|
PaymentMethod struct {
|
||||||
PayeePreferred PayeePreferred `json:"payee_preferred,omitempty"`
|
PayeePreferred PayeePreferred `json:"payee_preferred,omitempty"`
|
||||||
|
|
Loading…
Reference in New Issue
Block a user