Skip to content
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
134 changes: 130 additions & 4 deletions payments/setups/setups.go
Original file line number Diff line number Diff line change
Expand Up @@ -701,17 +701,57 @@ type PaymentMethodOptions struct {
type KlarnaPaymentMethod struct {
PaymentMethodBase

// Action is the next available action for the payment method.
// [Optional]
// Read only
Action *KlarnaAction `json:"action,omitempty"`

// AccountHolder is the account holder details returned by Klarna after the shopper completes
// verification.
// [Optional]
// Read only
AccountHolder *KlarnaAccountHolder `json:"account_holder,omitempty"`

// PaymentMethodOptions holds the payment method options. Not part of the current Payment Setup schema.
// Iban is the IBAN of the account used to complete Klarna verification.
// [Optional]
// Read only
Iban string `json:"iban,omitempty"`

// PaymentMethodOptions holds the payment method options.
// [Optional]
//
// Deprecated: not part of the API spec; the API no longer sends it and ignores it on
// requests. Use Action instead. It will be removed in the next major version.
PaymentMethodOptions *PaymentMethodOptions `json:"payment_method_options,omitempty"`
}

// KlarnaActionType is the type of the next available action for the Klarna payment method.
type KlarnaActionType string

const (
// KlarnaActionTypeSdk completes the payment with the Klarna SDK, using the client_token and
// session_id.
KlarnaActionTypeSdk KlarnaActionType = "sdk"
)

// KlarnaAction is the next available action for the Klarna payment method.
type KlarnaAction struct {
// Type is the type of action.
// [Optional]
// Read only
Type KlarnaActionType `json:"type,omitempty"`

// ClientToken is the client token for the Klarna SDK.
// [Optional]
// Read only
ClientToken string `json:"client_token,omitempty"`

// SessionId is the session ID.
// [Optional]
// Read only
SessionId string `json:"session_id,omitempty"`
}

// KlarnaAccountHolder is the account holder details returned by Klarna after the shopper
// completes verification.
type KlarnaAccountHolder struct {
Expand All @@ -726,19 +766,51 @@ type StcpayPaymentMethod struct {

// Otp is the one-time password (OTP) for stc pay.
// [Optional]
// Write only
Otp string `json:"otp,omitempty"`

// PaymentMethodOptions holds the payment method options. Not part of the current Payment Setup schema.
// Action is the next available action for the payment method.
// [Optional]
// Read only
Action *StcpayAction `json:"action,omitempty"`

// PaymentMethodOptions holds the payment method options.
// [Optional]
//
// Deprecated: not part of the API spec; the API no longer sends it and ignores it on
// requests. Use Action instead. It will be removed in the next major version.
PaymentMethodOptions *PaymentMethodOptions `json:"payment_method_options,omitempty"`
}

// StcpayActionType is the type of the next available action for the stc pay payment method.
type StcpayActionType string

const (
// StcpayActionTypeOtp asks for the one-time password the customer received.
StcpayActionTypeOtp StcpayActionType = "otp"
)

// StcpayAction is the next available action for the stc pay payment method.
type StcpayAction struct {
// Type is the type of action.
// [Optional]
// Read only
Type StcpayActionType `json:"type,omitempty"`
}

// TabbyPaymentMethod is the Tabby payment method's details and configuration.
type TabbyPaymentMethod struct {
PaymentMethodBase

// PaymentMethodOptions holds the payment method options. Not part of the current Payment Setup schema.
// PaymentTypes is the available payment types for Tabby (for example, "installments").
// [Optional]
PaymentTypes []string `json:"payment_types,omitempty"`

// PaymentMethodOptions holds the payment method options.
// [Optional]
//
// Deprecated: not part of the API spec; the API no longer sends it and ignores it on
// requests. There is no replacement. It will be removed in the next major version.
PaymentMethodOptions *PaymentMethodOptions `json:"payment_method_options,omitempty"`
}

Expand Down Expand Up @@ -1265,6 +1337,19 @@ type SepaPaymentMethod struct {
PaymentMethodOptions *PaymentMethodOptions `json:"payment_method_options,omitempty"`
}

// PaypalShippingPreference is where PayPal obtains the shipping information.
type PaypalShippingPreference string

const (
// PaypalShippingPreferenceNoShipping redacts the shipping address from the PayPal pages.
PaypalShippingPreferenceNoShipping PaypalShippingPreference = "no_shipping"
// PaypalShippingPreferenceGetFromFile obtains the shipping address from the customer's PayPal
// account.
PaypalShippingPreferenceGetFromFile PaypalShippingPreference = "get_from_file"
// PaypalShippingPreferenceSetProvidedAddress uses the shipping address provided by the merchant.
PaypalShippingPreferenceSetProvidedAddress PaypalShippingPreference = "set_provided_address"
)

// PaypalPaymentMethod is the PayPal payment method's details and configuration.
type PaypalPaymentMethod struct {
PaymentMethodBase
Expand All @@ -1279,11 +1364,52 @@ type PaypalPaymentMethod struct {
// Enum: "pay_now" "continue"
UserAction string `json:"user_action,omitempty"`

// PaymentMethodOptions holds the payment method options. Not part of the current Payment Setup schema.
// BrandName is the brand name to display in the PayPal checkout experience.
// [Optional]
// max 127 characters
BrandName string `json:"brand_name,omitempty"`

// ShippingPreference is where to obtain the shipping information.
// - no_shipping: redacts the shipping address from the PayPal pages.
// - get_from_file: obtains the shipping address from the customer's PayPal account.
// - set_provided_address: uses the shipping address provided by the merchant.
// [Optional]
ShippingPreference PaypalShippingPreference `json:"shipping_preference,omitempty"`

// Action is the next available action for the payment method.
// [Optional]
// Read only
Action *PaypalAction `json:"action,omitempty"`

// PaymentMethodOptions holds the payment method options.
// [Optional]
//
// Deprecated: not part of the API spec; the API no longer sends it and ignores it on
// requests. Use Action instead. It will be removed in the next major version.
PaymentMethodOptions *PaymentMethodOptions `json:"payment_method_options,omitempty"`
}

// PaypalActionType is the type of the next available action for the PayPal payment method.
type PaypalActionType string

const (
// PaypalActionTypeSdk completes the payment with the PayPal SDK, using the order_id.
PaypalActionTypeSdk PaypalActionType = "sdk"
)

// PaypalAction is the next available action for the PayPal payment method.
type PaypalAction struct {
// Type is the type of action.
// [Optional]
// Read only
Type PaypalActionType `json:"type,omitempty"`

// OrderId is the PayPal order ID to use with the PayPal SDK.
// [Optional]
// Read only
OrderId string `json:"order_id,omitempty"`
}

// BacsAccountHolderType is the type of Bacs account holder.
type BacsAccountHolderType string

Expand Down
151 changes: 151 additions & 0 deletions payments/setups/setups_fields_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1036,3 +1036,154 @@ func TestPaymentSetup_AutoConfirmMetadataAndInstrumentType(t *testing.T) {
assert.Equal(t, instrument, restoredInstrument)
assert.Equal(t, "NY2018", response.Metadata["coupon_code"])
}

// Klarna, PayPal, Tabby and stc pay carry their own action and properties directly on the
// payment method, with no payment_method_options wrapper.

func TestPaymentMethods_PaypalAndTabbyRequestWireKeys(t *testing.T) {
methods := PaymentMethods{
Paypal: &PaypalPaymentMethod{
PaymentMethodBase: PaymentMethodBase{Initialization: PaymentMethodInitializationEnabled},
UserAction: "pay_now",
BrandName: "Acme Corporation",
ShippingPreference: PaypalShippingPreferenceNoShipping,
},
Tabby: &TabbyPaymentMethod{
PaymentMethodBase: PaymentMethodBase{Initialization: PaymentMethodInitializationEnabled},
PaymentTypes: []string{"pay_later"},
},
Stcpay: &StcpayPaymentMethod{Otp: "123456"},
}

marshalled, err := json.Marshal(methods)
assert.NoError(t, err)
assert.JSONEq(t, `{
"paypal":{"initialization":"enabled","user_action":"pay_now","brand_name":"Acme Corporation",
"shipping_preference":"no_shipping"},
"tabby":{"initialization":"enabled","payment_types":["pay_later"]},
"stcpay":{"otp":"123456"}}`, string(marshalled))
assert.NotContains(t, string(marshalled), "payment_method_options")
assert.NotContains(t, string(marshalled), `"action"`)
}

func TestPaypalShippingPreference_EveryValueMatchesTheSpec(t *testing.T) {
assert.Equal(t, PaypalShippingPreference("no_shipping"), PaypalShippingPreferenceNoShipping)
assert.Equal(t, PaypalShippingPreference("get_from_file"), PaypalShippingPreferenceGetFromFile)
assert.Equal(t, PaypalShippingPreference("set_provided_address"), PaypalShippingPreferenceSetProvidedAddress)
}

func TestFlattenedMethodActionTypes_EveryValueMatchesTheSpec(t *testing.T) {
assert.Equal(t, KlarnaActionType("sdk"), KlarnaActionTypeSdk)
assert.Equal(t, PaypalActionType("sdk"), PaypalActionTypeSdk)
assert.Equal(t, StcpayActionType("otp"), StcpayActionTypeOtp)
}

func TestPaymentSetupResponse_DeserializesFlattenedMethodActions(t *testing.T) {
payload := `{"id":"ps_2Un2jYAVxLsfUfpOvLhEHRW8Ft1","payment_methods":{
"klarna":{"status":"action_required","flags":["missing_phone"],"initialization":"enabled",
"action":{"type":"sdk","client_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJzZXNzaW9uX2lkIiA6ICIw",
"session_id":"0b1d9815-165e-42e2-8867-35bc03789e00"},
"account_holder":{"name":"John Smith"},"iban":"DE89370400440532013000"},
"paypal":{"status":"action_required","initialization":"enabled","user_action":"pay_now",
"brand_name":"Acme Corporation","shipping_preference":"no_shipping",
"action":{"type":"sdk","order_id":"dd4b1a85-31de-404b-99f4-f143f8ba35ba"}},
"tabby":{"status":"ready","initialization":"enabled","payment_types":["pay_later"]},
"stcpay":{"status":"action_required","initialization":"enabled","action":{"type":"otp"}}}}`

var response PaymentSetupResponse
assert.NoError(t, json.Unmarshal([]byte(payload), &response))
methods := response.PaymentMethods

assert.Equal(t, KlarnaPaymentMethod{
PaymentMethodBase: PaymentMethodBase{
Status: "action_required",
Flags: []string{"missing_phone"},
Initialization: PaymentMethodInitializationEnabled,
},
Action: &KlarnaAction{
Type: KlarnaActionTypeSdk,
ClientToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJzZXNzaW9uX2lkIiA6ICIw",
SessionId: "0b1d9815-165e-42e2-8867-35bc03789e00",
},
AccountHolder: &KlarnaAccountHolder{Name: "John Smith"},
Iban: "DE89370400440532013000",
}, *methods.Klarna)

assert.Equal(t, PaypalPaymentMethod{
PaymentMethodBase: PaymentMethodBase{
Status: "action_required",
Initialization: PaymentMethodInitializationEnabled,
},
UserAction: "pay_now",
BrandName: "Acme Corporation",
ShippingPreference: PaypalShippingPreferenceNoShipping,
Action: &PaypalAction{Type: PaypalActionTypeSdk, OrderId: "dd4b1a85-31de-404b-99f4-f143f8ba35ba"},
}, *methods.Paypal)

assert.Equal(t, TabbyPaymentMethod{
PaymentMethodBase: PaymentMethodBase{Status: "ready", Initialization: PaymentMethodInitializationEnabled},
PaymentTypes: []string{"pay_later"},
}, *methods.Tabby)

assert.Equal(t, StcpayPaymentMethod{
PaymentMethodBase: PaymentMethodBase{
Status: "action_required",
Initialization: PaymentMethodInitializationEnabled,
},
Action: &StcpayAction{Type: StcpayActionTypeOtp},
}, *methods.Stcpay)
}

func TestFlattenedPaymentMethods_RoundTripKeepsEveryProperty(t *testing.T) {
base := PaymentMethodBase{
Status: "action_required",
Flags: []string{"missing_phone"},
Initialization: PaymentMethodInitializationEnabled,
}
original := PaymentMethods{
Klarna: &KlarnaPaymentMethod{
PaymentMethodBase: base,
Action: &KlarnaAction{
Type: KlarnaActionTypeSdk,
ClientToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJzZXNzaW9uX2lkIiA6ICIw",
SessionId: "0b1d9815-165e-42e2-8867-35bc03789e00",
},
AccountHolder: &KlarnaAccountHolder{Name: "John Smith"},
Iban: "DE89370400440532013000",
},
Paypal: &PaypalPaymentMethod{
PaymentMethodBase: base,
UserAction: "continue",
BrandName: "Acme Corporation",
ShippingPreference: PaypalShippingPreferenceSetProvidedAddress,
Action: &PaypalAction{Type: PaypalActionTypeSdk, OrderId: "dd4b1a85-31de-404b-99f4-f143f8ba35ba"},
},
Tabby: &TabbyPaymentMethod{
PaymentMethodBase: base,
PaymentTypes: []string{"pay_later", "installments"},
},
Stcpay: &StcpayPaymentMethod{
PaymentMethodBase: base,
Otp: "123456",
Action: &StcpayAction{Type: StcpayActionTypeOtp},
},
}

marshalled, err := json.Marshal(original)
assert.NoError(t, err)
body := string(marshalled)
assert.Contains(t, body, `"client_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJzZXNzaW9uX2lkIiA6ICIw"`)
assert.Contains(t, body, `"session_id":"0b1d9815-165e-42e2-8867-35bc03789e00"`)
assert.Contains(t, body, `"iban":"DE89370400440532013000"`)
assert.Contains(t, body, `"paypal":`)
assert.Contains(t, body, `"brand_name":"Acme Corporation"`)
assert.Contains(t, body, `"shipping_preference":"set_provided_address"`)
assert.Contains(t, body, `"order_id":"dd4b1a85-31de-404b-99f4-f143f8ba35ba"`)
assert.Contains(t, body, `"payment_types":["pay_later","installments"]`)
assert.Contains(t, body, `"otp":"123456"`)
assert.Contains(t, body, `"action":{"type":"otp"}`)

var decoded PaymentMethods
assert.NoError(t, json.Unmarshal(marshalled, &decoded))
assert.Equal(t, original, decoded)
}
2 changes: 1 addition & 1 deletion test/payment_setups_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var (
ProcessingChannelId: os.Getenv("CHECKOUT_PROCESSING_CHANNEL_ID"),
PaymentMethods: &setups.PaymentMethods{
Klarna: &setups.KlarnaPaymentMethod{
// status, flags and payment_method_options.action are read-only
// status, flags and action are read-only
// response fields; only initialization is writable on the request.
PaymentMethodBase: setups.PaymentMethodBase{
Initialization: setups.PaymentMethodInitializationEnabled,
Expand Down
Loading