forked from linode/linodego
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added support for missing Payment-related endpoints (linode#601)
* Added support for missing payment endpoints * Rename PromoCode struct to Promotion
- Loading branch information
1 parent
c6b4678
commit f96575c
Showing
10 changed files
with
448 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
package linodego | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/linode/linodego/internal/parseabletime" | ||
) | ||
|
||
// PaymentMethod represents a PaymentMethod object | ||
type PaymentMethod struct { | ||
// The unique ID of the Payment Method. | ||
ID int `json:"id"` | ||
|
||
// When the Payment Method was created. | ||
Created *time.Time `json:"created"` | ||
|
||
// Whether this Payment Method is the default method for automatically processing service charges. | ||
IsDefault bool `json:"is_default"` | ||
|
||
// The type of Payment Method. | ||
Type string `json:"type"` | ||
|
||
// The detailed data for the Payment Method, which can be of varying types. | ||
Data interface{} `json:"data"` | ||
} | ||
|
||
// PaymentMethodDataCreditCard represents a PaymentMethodDataCreditCard object | ||
type PaymentMethodDataCreditCard struct { | ||
// The type of credit card. | ||
CardType string `json:"card_type"` | ||
|
||
// The expiration month and year of the credit card. | ||
Expiry string `json:"expiry"` | ||
|
||
// The last four digits of the credit card number. | ||
LastFour string `json:"last_four"` | ||
} | ||
|
||
// PaymentMethodDataGooglePay represents a PaymentMethodDataGooglePay object | ||
type PaymentMethodDataGooglePay struct { | ||
// The type of credit card. | ||
CardType string `json:"card_type"` | ||
|
||
// The expiration month and year of the credit card. | ||
Expiry string `json:"expiry"` | ||
|
||
// The last four digits of the credit card number. | ||
LastFour string `json:"last_four"` | ||
} | ||
|
||
// PaymentMethodDataPaypal represents a PaymentMethodDataPaypal object | ||
type PaymentMethodDataPaypal struct { | ||
// The email address associated with your PayPal account. | ||
Email string `json:"email"` | ||
|
||
// PayPal Merchant ID associated with your PayPal account. | ||
PaypalID string `json:"paypal_id"` | ||
} | ||
|
||
// PaymentMethodCreateOptions fields are those accepted by CreatePaymentMethod | ||
type PaymentMethodCreateOptions struct { | ||
// Whether this Payment Method is the default method for automatically processing service charges. | ||
IsDefault bool `json:"is_default"` | ||
|
||
// The type of Payment Method. Alternative payment methods including Google Pay and PayPal can be added using the Cloud Manager. | ||
Type string `json:"type"` | ||
|
||
// An object representing the credit card information you have on file with Linode to make Payments against your Account. | ||
Data *PaymentMethodCreateOptionsData `json:"data"` | ||
} | ||
|
||
type PaymentMethodCreateOptionsData struct { | ||
// Your credit card number. No spaces or hyphens (-) allowed. | ||
CardNumber string `json:"card_number"` | ||
|
||
// CVV (Card Verification Value) of the credit card, typically found on the back of the card. | ||
CVV string `json:"cvv"` | ||
|
||
// A value from 1-12 representing the expiration month of your credit card. | ||
ExpiryMonth int `json:"expiry_month"` | ||
|
||
// A four-digit integer representing the expiration year of your credit card. | ||
ExpiryYear int `json:"expiry_year"` | ||
} | ||
|
||
// UnmarshalJSON implements the json.Unmarshaler interface | ||
func (i *PaymentMethod) UnmarshalJSON(b []byte) error { | ||
if len(b) == 0 || string(b) == "{}" || string(b) == "null" { | ||
return nil | ||
} | ||
|
||
type Mask PaymentMethod | ||
|
||
pm := &struct { | ||
*Mask | ||
Created *parseabletime.ParseableTime `json:"created"` | ||
Data json.RawMessage `json:"data"` | ||
}{ | ||
Mask: (*Mask)(i), | ||
} | ||
|
||
if err := json.Unmarshal(b, &pm); err != nil { | ||
return err | ||
} | ||
|
||
// Process Data based on the Type field | ||
switch i.Type { | ||
case "credit_card": | ||
var creditCardData PaymentMethodDataCreditCard | ||
if err := json.Unmarshal(pm.Data, &creditCardData); err != nil { | ||
return err | ||
} | ||
i.Data = creditCardData | ||
case "google_pay": | ||
var googlePayData PaymentMethodDataGooglePay | ||
if err := json.Unmarshal(pm.Data, &googlePayData); err != nil { | ||
return err | ||
} | ||
i.Data = googlePayData | ||
case "paypal": | ||
var paypalData PaymentMethodDataPaypal | ||
if err := json.Unmarshal(pm.Data, &paypalData); err != nil { | ||
return err | ||
} | ||
i.Data = paypalData | ||
default: | ||
return fmt.Errorf("unknown payment method type: %s", i.Type) | ||
} | ||
|
||
i.Created = (*time.Time)(pm.Created) | ||
return nil | ||
} | ||
|
||
// ListPaymentMethods lists PaymentMethods | ||
func (c *Client) ListPaymentMethods(ctx context.Context, opts *ListOptions) ([]PaymentMethod, error) { | ||
return getPaginatedResults[PaymentMethod](ctx, c, "account/payment-methods", opts) | ||
} | ||
|
||
// GetPaymentMethod gets the payment method with the provided ID | ||
func (c *Client) GetPaymentMethod(ctx context.Context, paymentMethodID int) (*PaymentMethod, error) { | ||
e := formatAPIPath("account/payment-methods/%d", paymentMethodID) | ||
return doGETRequest[PaymentMethod](ctx, c, e) | ||
} | ||
|
||
// DeletePaymentMethod deletes the payment method with the provided ID | ||
func (c *Client) DeletePaymentMethod(ctx context.Context, paymentMethodID int) error { | ||
e := formatAPIPath("account/payment-methods/%d", paymentMethodID) | ||
return doDELETERequest(ctx, c, e) | ||
} | ||
|
||
// AddPaymentMethod adds the provided payment method to the account | ||
func (c *Client) AddPaymentMethod(ctx context.Context, opts PaymentMethodCreateOptions) error { | ||
_, err := doPOSTRequest[PaymentMethod, any](ctx, c, "account/payment-methods", opts) | ||
return err | ||
} | ||
|
||
// SetDefaultPaymentMethod sets the payment method with the provided ID as the default | ||
func (c *Client) SetDefaultPaymentMethod(ctx context.Context, paymentMethodID int) error { | ||
e := formatAPIPath("account/payment-methods/%d", paymentMethodID) | ||
_, err := doPOSTRequest[PaymentMethod, any](ctx, c, e) | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package linodego | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"time" | ||
|
||
"github.com/linode/linodego/internal/parseabletime" | ||
) | ||
|
||
// Promotion represents a Promotion object | ||
type Promotion struct { | ||
// The amount available to spend per month. | ||
CreditMonthlyCap string `json:"credit_monthly_cap"` | ||
|
||
// The total amount of credit left for this promotion. | ||
CreditRemaining string `json:"credit_remaining"` | ||
|
||
// A detailed description of this promotion. | ||
Description string `json:"description"` | ||
|
||
// When this promotion's credits expire. | ||
ExpirationDate *time.Time `json:"-"` | ||
|
||
// The location of an image for this promotion. | ||
ImageURL string `json:"image_url"` | ||
|
||
// The service to which this promotion applies. | ||
ServiceType string `json:"service_type"` | ||
|
||
// Short details of this promotion. | ||
Summary string `json:"summary"` | ||
|
||
// The amount of credit left for this month for this promotion. | ||
ThisMonthCreditRemaining string `json:"this_month_credit_remaining"` | ||
} | ||
|
||
// PromoCodeCreateOptions fields are those accepted by AddPromoCode | ||
type PromoCodeCreateOptions struct { | ||
// The Promo Code. | ||
PromoCode string `json:"promo_code"` | ||
} | ||
|
||
// UnmarshalJSON implements the json.Unmarshaler interface | ||
func (i *Promotion) UnmarshalJSON(b []byte) error { | ||
type Mask Promotion | ||
|
||
p := struct { | ||
*Mask | ||
ExpirationDate *parseabletime.ParseableTime `json:"date"` | ||
}{ | ||
Mask: (*Mask)(i), | ||
} | ||
|
||
if err := json.Unmarshal(b, &p); err != nil { | ||
return err | ||
} | ||
|
||
i.ExpirationDate = (*time.Time)(p.ExpirationDate) | ||
|
||
return nil | ||
} | ||
|
||
// AddPromoCode adds the provided promo code to the account | ||
func (c *Client) AddPromoCode(ctx context.Context, opts PromoCodeCreateOptions) (*Promotion, error) { | ||
return doPOSTRequest[Promotion, any](ctx, c, "account/promo-codes", opts) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package unit | ||
|
||
import ( | ||
"context" | ||
"github.com/jarcoal/httpmock" | ||
"github.com/linode/linodego" | ||
"github.com/stretchr/testify/assert" | ||
"testing" | ||
) | ||
|
||
func TestAccountPaymentMethods_Get(t *testing.T) { | ||
fixtureData, err := fixtures.GetFixture("account_payment_methods_get") | ||
assert.NoError(t, err) | ||
|
||
var base ClientBaseCase | ||
base.SetUp(t) | ||
defer base.TearDown(t) | ||
|
||
base.MockGet("account/payment-methods/123", fixtureData) | ||
|
||
pm, err := base.Client.GetPaymentMethod(context.Background(), 123) | ||
assert.NoError(t, err) | ||
|
||
assert.Equal(t, 123, pm.ID) | ||
assert.Equal(t, true, pm.IsDefault) | ||
assert.Equal(t, "credit_card", pm.Type) | ||
assert.Equal(t, linodego.PaymentMethodDataCreditCard{ | ||
CardType: "Discover", | ||
Expiry: "06/2022", | ||
LastFour: "1234", | ||
}, pm.Data) | ||
} | ||
|
||
func TestAccountPaymentMethods_List(t *testing.T) { | ||
fixtureData, err := fixtures.GetFixture("account_payment_methods_list") | ||
assert.NoError(t, err) | ||
|
||
var base ClientBaseCase | ||
base.SetUp(t) | ||
defer base.TearDown(t) | ||
|
||
base.MockGet("account/payment-methods", fixtureData) | ||
|
||
methods, err := base.Client.ListPaymentMethods(context.Background(), nil) | ||
assert.NoError(t, err) | ||
|
||
assert.Equal(t, 1, len(methods)) | ||
pm := methods[0] | ||
assert.Equal(t, 123, pm.ID) | ||
assert.Equal(t, true, pm.IsDefault) | ||
assert.Equal(t, "credit_card", pm.Type) | ||
assert.Equal(t, linodego.PaymentMethodDataCreditCard{ | ||
CardType: "Discover", | ||
Expiry: "06/2022", | ||
LastFour: "1234", | ||
}, pm.Data) | ||
} | ||
|
||
func TestAccountPaymentMethods_Add(t *testing.T) { | ||
client := createMockClient(t) | ||
|
||
card := linodego.PaymentMethodCreateOptionsData{ | ||
CardNumber: "1234123412341234", | ||
CVV: "123", | ||
ExpiryMonth: 3, | ||
ExpiryYear: 2028, | ||
} | ||
|
||
requestData := linodego.PaymentMethodCreateOptions{ | ||
Data: &card, | ||
IsDefault: true, | ||
Type: "credit_card", | ||
} | ||
|
||
httpmock.RegisterRegexpResponder("POST", mockRequestURL(t, "account/payment-methods"), | ||
mockRequestBodyValidate(t, requestData, nil)) | ||
|
||
if err := client.AddPaymentMethod(context.Background(), requestData); err != nil { | ||
t.Fatal(err) | ||
} | ||
} | ||
|
||
func TestAccountPaymentMethods_Delete(t *testing.T) { | ||
client := createMockClient(t) | ||
|
||
httpmock.RegisterRegexpResponder("DELETE", mockRequestURL(t, "account/payment-methods/123"), httpmock.NewStringResponder(200, "{}")) | ||
|
||
if err := client.DeletePaymentMethod(context.Background(), 123); err != nil { | ||
t.Fatal(err) | ||
} | ||
} | ||
|
||
func TestAccountPaymentMethods_SetDefault(t *testing.T) { | ||
client := createMockClient(t) | ||
|
||
httpmock.RegisterRegexpResponder("POST", mockRequestURL(t, "account/payment-methods/123"), | ||
httpmock.NewStringResponder(200, "{}")) | ||
|
||
if err := client.SetDefaultPaymentMethod(context.Background(), 123); err != nil { | ||
t.Fatal(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package unit | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"github.com/linode/linodego" | ||
"github.com/stretchr/testify/assert" | ||
"testing" | ||
) | ||
|
||
func TestAccountPayments_Create(t *testing.T) { | ||
fixtureData, err := fixtures.GetFixture("account_payment_create") | ||
assert.NoError(t, err) | ||
|
||
var base ClientBaseCase | ||
base.SetUp(t) | ||
defer base.TearDown(t) | ||
|
||
base.MockPost("account/payments", fixtureData) | ||
|
||
requestData := linodego.PaymentCreateOptions{ | ||
CVV: "123", | ||
USD: json.Number("120.50"), | ||
} | ||
|
||
payment, err := base.Client.CreatePayment(context.Background(), requestData) | ||
if err != nil { | ||
t.Fatalf("Error creating payment: %v", err) | ||
} | ||
|
||
assert.Equal(t, 123, payment.ID) | ||
assert.Equal(t, json.Number("120.50"), payment.USD) | ||
} |
Oops, something went wrong.