diff --git a/README.md b/README.md index 72fe5aa..12605d6 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ try { Service methods also accept associative arrays as request payloads. For typed usage, prefer DTOs from `\SumUp\Types\...` with named arguments, or use `TypeName::fromArray([...])` when you already have associative array data. +Enum properties accept both the generated enum and its backing scalar type. Known values are hydrated into enum cases; new values returned by the API are preserved unchanged as scalars so they do not prevent reading the response. Requests also accept these scalar values. When reading an enum property, handle both forms rather than assuming it always has a `value` property. Calling a native enum's `from()` method directly still requires a known value. + ### Providing API Key Programmatically If you prefer to provide the API key directly in your code instead of using the environment variable: diff --git a/codegen/pkg/generator/generator.go b/codegen/pkg/generator/generator.go index d452438..b22789d 100644 --- a/codegen/pkg/generator/generator.go +++ b/codegen/pkg/generator/generator.go @@ -300,7 +300,7 @@ func (g *Generator) buildRequestConstructor(properties []phpProperty) string { buf.WriteString(" * Create request DTO.\n") buf.WriteString(" *\n") for _, prop := range constructorProperties(properties) { - fmt.Fprintf(&buf, " * @param %s $%s\n", g.constructorParamDocType(prop), prop.Name) + fmt.Fprintf(&buf, " * @param %s $%s\n", g.propertyDocType(prop), prop.Name) } buf.WriteString(" */\n") buf.WriteString(" public function __construct(") @@ -309,7 +309,7 @@ func (g *Generator) buildRequestConstructor(properties []phpProperty) string { buf.WriteString("\n") for idx, prop := range constructorProps { buf.WriteString(" ") - buf.WriteString(g.constructorParamType(prop)) + buf.WriteString(g.propertyTypeHint(prop)) buf.WriteString(" $") buf.WriteString(prop.Name) if prop.Optional { @@ -393,7 +393,7 @@ func requiredProperties(properties []phpProperty) []phpProperty { return result } -func (g *Generator) constructorParamDocType(prop phpProperty) string { +func (g *Generator) propertyDocType(prop phpProperty) string { docType := prop.DocType if docType == "" { docType = "mixed" @@ -407,7 +407,7 @@ func (g *Generator) constructorParamDocType(prop phpProperty) string { return docType } -func (g *Generator) constructorParamType(prop phpProperty) string { +func (g *Generator) propertyTypeHint(prop phpProperty) string { paramType := prop.Type if paramType == "" { paramType = "mixed" diff --git a/codegen/pkg/generator/properties.go b/codegen/pkg/generator/properties.go index b8a0ea0..47a8244 100644 --- a/codegen/pkg/generator/properties.go +++ b/codegen/pkg/generator/properties.go @@ -170,23 +170,11 @@ func (g *Generator) renderProperty(prop phpProperty) string { } } b.WriteString(" *\n") - docType := prop.DocType - if prop.Optional || prop.Nullable { - if !strings.Contains(docType, "null") { - docType += "|null" - } - } + docType := g.propertyDocType(prop) fmt.Fprintf(&b, " * @var %s\n", docType) b.WriteString(" */\n") - propertyType := prop.Type - if (prop.Optional || prop.Nullable) && propertyType != "mixed" && !strings.HasPrefix(propertyType, "?") { - propertyType = "?" + propertyType - } - - if propertyType == "" { - propertyType = "mixed" - } + propertyType := g.propertyTypeHint(prop) if prop.Optional { fmt.Fprintf(&b, " public %s $%s = null;\n\n", propertyType, prop.Name) diff --git a/codegen/pkg/generator/properties_test.go b/codegen/pkg/generator/properties_test.go index 3f27c4a..045afe7 100644 --- a/codegen/pkg/generator/properties_test.go +++ b/codegen/pkg/generator/properties_test.go @@ -68,7 +68,55 @@ func TestRequiredNullableProperty(t *testing.T) { if !strings.Contains(generated, "public ?string $value;") { t.Fatalf("renderProperty() did not allow null:\n%s", generated) } - if got, want := generator.constructorParamType(property), "?string"; got != want { - t.Fatalf("constructorParamType() = %q, want %q", got, want) + if got, want := generator.propertyTypeHint(property), "?string"; got != want { + t.Fatalf("propertyTypeHint() = %q, want %q", got, want) + } +} + +func TestOpenEnumProperty(t *testing.T) { + t.Parallel() + + generator := New(Config{}) + generator.enumsByTag = map[string][]enumDefinition{ + typesTagKey: { + {Name: "Status", Type: "string"}, + {Name: "Code", Type: "int"}, + }, + } + tests := map[string]struct { + property phpProperty + wantType string + }{ + "required": { + property: phpProperty{Name: "value", Type: "Status", DocType: "Status"}, + wantType: "Status|string", + }, + "optional": { + property: phpProperty{Name: "value", Type: "Status", DocType: "Status", Optional: true}, + wantType: "Status|string|null", + }, + "nullable": { + property: phpProperty{Name: "value", Type: "Status", DocType: "Status", Nullable: true}, + wantType: "Status|string|null", + }, + "integer backing type": { + property: phpProperty{Name: "value", Type: `\SumUp\Types\Code`, DocType: `\SumUp\Types\Code`}, + wantType: `\SumUp\Types\Code|int`, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + generated := generator.renderProperty(tt.property) + for _, want := range []string{"@var " + tt.wantType, "public " + tt.wantType + " $value"} { + if !strings.Contains(generated, want) { + t.Fatalf("renderProperty() missing %q:\n%s", want, generated) + } + } + if got := generator.propertyTypeHint(tt.property); got != tt.wantType { + t.Fatalf("propertyTypeHint() = %q, want %q", got, tt.wantType) + } + }) } } diff --git a/src/Hydrator.php b/src/Hydrator.php index b30b9a3..64d138a 100644 --- a/src/Hydrator.php +++ b/src/Hydrator.php @@ -5,6 +5,7 @@ use ReflectionClass; use ReflectionNamedType; use ReflectionProperty; +use ReflectionUnionType; /** * Hydrates SDK models from associative arrays or stdClass payloads. @@ -98,6 +99,16 @@ private static function castValue($value, ReflectionProperty $property) } $type = $property->getType(); + if ($type instanceof ReflectionUnionType) { + foreach ($type->getTypes() as $member) { + if ($member instanceof ReflectionNamedType && enum_exists($member->getName())) { + return self::castEnumValue($value, $member->getName()); + } + } + + return $value; + } + if (!$type instanceof ReflectionNamedType) { return $value; } @@ -146,10 +157,6 @@ private static function castEnumValue($value, $enumClass) } } - if (method_exists($enumClass, 'from')) { - return $enumClass::from($value); - } - return $value; } @@ -245,6 +252,10 @@ private static function castArrayItem($item, $itemType, ReflectionProperty $prop $className = ltrim($itemType, '\\'); } + if (enum_exists($className)) { + return self::castEnumValue($item, $className); + } + if (!class_exists($className)) { return $item; } diff --git a/src/Types/BadRequestErrors.php b/src/Types/BadRequestErrors.php index 24321d5..6a4022c 100644 --- a/src/Types/BadRequestErrors.php +++ b/src/Types/BadRequestErrors.php @@ -16,8 +16,8 @@ class BadRequestErrors /** * Key indicating type of error * - * @var BadRequestErrorsType + * @var BadRequestErrorsType|string */ - public BadRequestErrorsType $type; + public BadRequestErrorsType|string $type; } diff --git a/src/Types/Card.php b/src/Types/Card.php index 0bfa761..8c68783 100644 --- a/src/Types/Card.php +++ b/src/Types/Card.php @@ -54,8 +54,8 @@ class Card /** * Issuing card network of the payment card used for the transaction. * - * @var CardType + * @var CardType|string */ - public CardType $type; + public CardType|string $type; } diff --git a/src/Types/CardResponse.php b/src/Types/CardResponse.php index 82f6133..ff2ed90 100644 --- a/src/Types/CardResponse.php +++ b/src/Types/CardResponse.php @@ -19,8 +19,8 @@ class CardResponse /** * Issuing card network of the payment card used for the transaction. * - * @var CardResponseType|null + * @var CardResponseType|string|null */ - public ?CardResponseType $type = null; + public CardResponseType|string|null $type = null; } diff --git a/src/Types/Checkout.php b/src/Types/Checkout.php index 6f763a8..659caca 100644 --- a/src/Types/Checkout.php +++ b/src/Types/Checkout.php @@ -26,9 +26,9 @@ class Checkout /** * Three-letter [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code of the amount. * - * @var CheckoutCurrency|null + * @var CheckoutCurrency|string|null */ - public ?CheckoutCurrency $currency = null; + public CheckoutCurrency|string|null $currency = null; /** * Short unique identifier for the merchant that receives the payment. @@ -61,9 +61,9 @@ class Checkout /** * Current high-level state of the checkout. `PENDING` means the checkout exists but is not yet completed, `PAID` means a payment succeeded, `FAILED` means the latest processing attempt failed, and `EXPIRED` means the checkout can no longer be processed. * - * @var CheckoutStatus|null + * @var CheckoutStatus|string|null */ - public ?CheckoutStatus $status = null; + public CheckoutStatus|string|null $status = null; /** * The timestamp of when the checkout was created. diff --git a/src/Types/CheckoutCreateRequest.php b/src/Types/CheckoutCreateRequest.php index eec7917..df6b877 100644 --- a/src/Types/CheckoutCreateRequest.php +++ b/src/Types/CheckoutCreateRequest.php @@ -26,9 +26,9 @@ class CheckoutCreateRequest /** * Three-letter [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code of the amount. * - * @var CheckoutCreateRequestCurrency + * @var CheckoutCreateRequestCurrency|string */ - public CheckoutCreateRequestCurrency $currency; + public CheckoutCreateRequestCurrency|string $currency; /** * Short unique identifier for the merchant that should receive the payment. @@ -61,9 +61,9 @@ class CheckoutCreateRequest /** * Business purpose of the checkout. Use `CHECKOUT` for a standard payment and `SETUP_RECURRING_PAYMENT` when collecting consent and payment details for future recurring charges. * - * @var CheckoutCreateRequestPurpose|null + * @var CheckoutCreateRequestPurpose|string|null */ - public ?CheckoutCreateRequestPurpose $purpose = null; + public CheckoutCreateRequestPurpose|string|null $purpose = null; /** * Optional expiration timestamp. The checkout must be processed before this moment, otherwise it becomes unusable. If omitted, the checkout does not have an explicit expiry time. diff --git a/src/Types/CheckoutUpdateRequest.php b/src/Types/CheckoutUpdateRequest.php index 5cce4fa..6c19056 100644 --- a/src/Types/CheckoutUpdateRequest.php +++ b/src/Types/CheckoutUpdateRequest.php @@ -19,9 +19,9 @@ class CheckoutUpdateRequest /** * Three-letter [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code of the amount. * - * @var CheckoutUpdateRequestCurrency|null + * @var CheckoutUpdateRequestCurrency|string|null */ - public ?CheckoutUpdateRequestCurrency $currency = null; + public CheckoutUpdateRequestCurrency|string|null $currency = null; /** * Updated short merchant-defined description shown in SumUp tools and reporting. diff --git a/src/Types/CreateReaderCheckoutRequest.php b/src/Types/CreateReaderCheckoutRequest.php index cededd1..37003fa 100644 --- a/src/Types/CreateReaderCheckoutRequest.php +++ b/src/Types/CreateReaderCheckoutRequest.php @@ -29,9 +29,9 @@ class CreateReaderCheckoutRequest * The card type of the card used for the transaction. * Is is required only for some countries (e.g: Brazil). * - * @var CreateReaderCheckoutRequestCardType|null + * @var CreateReaderCheckoutRequestCardType|string|null */ - public ?CreateReaderCheckoutRequestCardType $cardType = null; + public CreateReaderCheckoutRequestCardType|string|null $cardType = null; /** * Description of the checkout to be shown in the Merchant Sales diff --git a/src/Types/Event.php b/src/Types/Event.php index 33ebee4..02fc88f 100644 --- a/src/Types/Event.php +++ b/src/Types/Event.php @@ -26,9 +26,9 @@ class Event /** * Type of the transaction event. * - * @var EventType|null + * @var EventType|string|null */ - public ?EventType $type = null; + public EventType|string|null $type = null; /** * Status of the transaction event. @@ -41,9 +41,9 @@ class Event * - `SUCCESSFUL`: The event completed successfully. Use this as the generic terminal success status for event types that do not expose a more specific business outcome such as `PAID_OUT` or `REFUNDED`. * - `FAILED`: The event could not be completed. Typical examples are a payout that could not be executed or an event that was rejected during processing. * - * @var EventStatus|null + * @var EventStatus|string|null */ - public ?EventStatus $status = null; + public EventStatus|string|null $status = null; /** * Amount associated with the transaction event, in major units. diff --git a/src/Types/FinancialPayout.php b/src/Types/FinancialPayout.php index f2f7789..23f126e 100644 --- a/src/Types/FinancialPayout.php +++ b/src/Types/FinancialPayout.php @@ -23,9 +23,9 @@ class FinancialPayout /** * High-level payout record category. * - * @var FinancialPayoutType + * @var FinancialPayoutType|string */ - public FinancialPayoutType $type; + public FinancialPayoutType|string $type; /** * Amount of the payout or deduction in major units. @@ -58,9 +58,9 @@ class FinancialPayout /** * Merchant-facing outcome of the payout record. * - * @var FinancialPayoutStatus + * @var FinancialPayoutStatus|string */ - public FinancialPayoutStatus $status; + public FinancialPayoutStatus|string $status; /** * Processor or payout reference associated with the record. diff --git a/src/Types/GetReaderCheckoutResponseData.php b/src/Types/GetReaderCheckoutResponseData.php index 8bf43ec..57b71f3 100644 --- a/src/Types/GetReaderCheckoutResponseData.php +++ b/src/Types/GetReaderCheckoutResponseData.php @@ -9,9 +9,9 @@ class GetReaderCheckoutResponseData /** * Type of the card. Required for some countries * - * @var GetReaderCheckoutResponseDataCardType|null + * @var GetReaderCheckoutResponseDataCardType|string|null */ - public ?GetReaderCheckoutResponseDataCardType $cardType; + public GetReaderCheckoutResponseDataCardType|string|null $cardType; /** * Unique identifier for the checkout @@ -58,9 +58,9 @@ class GetReaderCheckoutResponseData /** * Type of the payment. Required for some countries * - * @var GetReaderCheckoutResponseDataPaymentType + * @var GetReaderCheckoutResponseDataPaymentType|string */ - public GetReaderCheckoutResponseDataPaymentType $paymentType; + public GetReaderCheckoutResponseDataPaymentType|string $paymentType; /** * Reader firmware version @@ -79,9 +79,9 @@ class GetReaderCheckoutResponseData /** * Current status of the checkout * - * @var GetReaderCheckoutResponseDataStatus + * @var GetReaderCheckoutResponseDataStatus|string */ - public GetReaderCheckoutResponseDataStatus $status; + public GetReaderCheckoutResponseDataStatus|string $status; /** * Amount structure. diff --git a/src/Types/MandatePayload.php b/src/Types/MandatePayload.php index 612cb2f..aecbe43 100644 --- a/src/Types/MandatePayload.php +++ b/src/Types/MandatePayload.php @@ -12,9 +12,9 @@ class MandatePayload /** * Type of mandate to create for the saved payment instrument. * - * @var MandatePayloadType + * @var MandatePayloadType|string */ - public MandatePayloadType $type; + public MandatePayloadType|string $type; /** * Browser or client user agent observed when consent was collected. diff --git a/src/Types/MandateResponse.php b/src/Types/MandateResponse.php index 94f934c..392b33c 100644 --- a/src/Types/MandateResponse.php +++ b/src/Types/MandateResponse.php @@ -19,9 +19,9 @@ class MandateResponse /** * Current lifecycle status of the mandate. * - * @var MandateResponseStatus|null + * @var MandateResponseStatus|string|null */ - public ?MandateResponseStatus $status = null; + public MandateResponseStatus|string|null $status = null; /** * Short unique identifier for the merchant for which the mandate is valid. diff --git a/src/Types/Member.php b/src/Types/Member.php index e30ee14..5d737d9 100644 --- a/src/Types/Member.php +++ b/src/Types/Member.php @@ -61,9 +61,9 @@ class Member /** * The status of the membership. * - * @var MemberStatus + * @var MemberStatus|string */ - public MemberStatus $status; + public MemberStatus|string $status; /** * Set of user-defined key-value pairs attached to the object. Partial updates are not supported. When updating, always submit whole metadata. Maximum of 64 parameters are allowed in the object. diff --git a/src/Types/Membership.php b/src/Types/Membership.php index 1752915..d61eefc 100644 --- a/src/Types/Membership.php +++ b/src/Types/Membership.php @@ -71,9 +71,9 @@ class Membership /** * The status of the membership. * - * @var MembershipStatus + * @var MembershipStatus|string */ - public MembershipStatus $status; + public MembershipStatus|string $status; /** * Set of user-defined key-value pairs attached to the object. Partial updates are not supported. When updating, always submit whole metadata. Maximum of 64 parameters are allowed in the object. diff --git a/src/Types/MembershipUser.php b/src/Types/MembershipUser.php index 19a24a0..49b48f2 100644 --- a/src/Types/MembershipUser.php +++ b/src/Types/MembershipUser.php @@ -19,9 +19,9 @@ class MembershipUser /** * Type of the user account. * - * @var MembershipUserType + * @var MembershipUserType|string */ - public MembershipUserType $type; + public MembershipUserType|string $type; /** * End-User's preferred e-mail address. Its value MUST conform to the RFC 5322 [RFC5322] addr-spec syntax. The RP MUST NOT rely upon this value being unique, for unique identification use ID instead. diff --git a/src/Types/PaymentInstrumentResponse.php b/src/Types/PaymentInstrumentResponse.php index 16ef54d..49f4655 100644 --- a/src/Types/PaymentInstrumentResponse.php +++ b/src/Types/PaymentInstrumentResponse.php @@ -26,9 +26,9 @@ class PaymentInstrumentResponse /** * Type of the payment instrument. * - * @var PaymentInstrumentResponseType|null + * @var PaymentInstrumentResponseType|string|null */ - public ?PaymentInstrumentResponseType $type = null; + public PaymentInstrumentResponseType|string|null $type = null; /** * Details of the payment card. diff --git a/src/Types/PaymentInstrumentResponseCard.php b/src/Types/PaymentInstrumentResponseCard.php index 1faa856..247abf3 100644 --- a/src/Types/PaymentInstrumentResponseCard.php +++ b/src/Types/PaymentInstrumentResponseCard.php @@ -19,8 +19,8 @@ class PaymentInstrumentResponseCard /** * Issuing card network of the payment card used for the transaction. * - * @var PaymentInstrumentResponseCardType|null + * @var PaymentInstrumentResponseCardType|string|null */ - public ?PaymentInstrumentResponseCardType $type = null; + public PaymentInstrumentResponseCardType|string|null $type = null; } diff --git a/src/Types/ProcessCheckout.php b/src/Types/ProcessCheckout.php index 06384fc..8383499 100644 --- a/src/Types/ProcessCheckout.php +++ b/src/Types/ProcessCheckout.php @@ -12,9 +12,9 @@ class ProcessCheckout /** * Payment method used for this processing attempt. It determines which additional request fields are required. * - * @var ProcessCheckoutPaymentType + * @var ProcessCheckoutPaymentType|string */ - public ProcessCheckoutPaymentType $paymentType; + public ProcessCheckoutPaymentType|string $paymentType; /** * Number of installments for deferred payments. Available only to merchant users in Brazil. diff --git a/src/Types/Reader.php b/src/Types/Reader.php index 79f6edd..aa2f30e 100644 --- a/src/Types/Reader.php +++ b/src/Types/Reader.php @@ -31,9 +31,9 @@ class Reader * - `paired` - The reader is paired with a merchant account and can be used with SumUp APIs. * - `expired` - The pairing is expired and no longer usable with the account. The resource needs to get recreated. * - * @var ReaderStatus + * @var ReaderStatus|string */ - public ReaderStatus $status; + public ReaderStatus|string $status; /** * Information about the underlying physical device. diff --git a/src/Types/ReaderDevice.php b/src/Types/ReaderDevice.php index e19425c..ac77b97 100644 --- a/src/Types/ReaderDevice.php +++ b/src/Types/ReaderDevice.php @@ -19,8 +19,8 @@ class ReaderDevice /** * Identifier of the model of the device. * - * @var ReaderDeviceModel + * @var ReaderDeviceModel|string */ - public ReaderDeviceModel $model; + public ReaderDeviceModel|string $model; } diff --git a/src/Types/ReceiptEvent.php b/src/Types/ReceiptEvent.php index 1509c80..1e7e3c9 100644 --- a/src/Types/ReceiptEvent.php +++ b/src/Types/ReceiptEvent.php @@ -26,9 +26,9 @@ class ReceiptEvent /** * Type of the transaction event. * - * @var ReceiptEventType|null + * @var ReceiptEventType|string|null */ - public ?ReceiptEventType $type = null; + public ReceiptEventType|string|null $type = null; /** * Status of the transaction event. @@ -41,9 +41,9 @@ class ReceiptEvent * - `SUCCESSFUL`: The event completed successfully. Use this as the generic terminal success status for event types that do not expose a more specific business outcome such as `PAID_OUT` or `REFUNDED`. * - `FAILED`: The event could not be completed. Typical examples are a payout that could not be executed or an event that was rejected during processing. * - * @var ReceiptEventStatus|null + * @var ReceiptEventStatus|string|null */ - public ?ReceiptEventStatus $status = null; + public ReceiptEventStatus|string|null $status = null; /** * Amount associated with the transaction event, in major units. diff --git a/src/Types/ReceiptTransaction.php b/src/Types/ReceiptTransaction.php index c86bfbe..753f2d4 100644 --- a/src/Types/ReceiptTransaction.php +++ b/src/Types/ReceiptTransaction.php @@ -117,9 +117,9 @@ class ReceiptTransaction /** * Whether the transaction was processed as credit or debit. * - * @var ReceiptTransactionProcessAs|null + * @var ReceiptTransactionProcessAs|string|null */ - public ?ReceiptTransactionProcessAs $processAs = null; + public ReceiptTransactionProcessAs|string|null $processAs = null; /** * Products associated with the transaction. diff --git a/src/Types/StatusResponseData.php b/src/Types/StatusResponseData.php index 67e1e4a..0a1c292 100644 --- a/src/Types/StatusResponseData.php +++ b/src/Types/StatusResponseData.php @@ -23,9 +23,9 @@ class StatusResponseData /** * Type of connection used by the device * - * @var StatusResponseDataConnectionType|null + * @var StatusResponseDataConnectionType|string|null */ - public ?StatusResponseDataConnectionType $connectionType = null; + public StatusResponseDataConnectionType|string|null $connectionType = null; /** * Firmware version of the device @@ -44,15 +44,15 @@ class StatusResponseData /** * Latest state of the device * - * @var StatusResponseDataState|null + * @var StatusResponseDataState|string|null */ - public ?StatusResponseDataState $state = null; + public StatusResponseDataState|string|null $state = null; /** * Status of a device * - * @var StatusResponseDataStatus + * @var StatusResponseDataStatus|string */ - public StatusResponseDataStatus $status; + public StatusResponseDataStatus|string $status; } diff --git a/src/Types/TransactionBase.php b/src/Types/TransactionBase.php index 2fdf544..8b58828 100644 --- a/src/Types/TransactionBase.php +++ b/src/Types/TransactionBase.php @@ -33,9 +33,9 @@ class TransactionBase /** * Three-letter [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code of the amount. * - * @var TransactionBaseCurrency|null + * @var TransactionBaseCurrency|string|null */ - public ?TransactionBaseCurrency $currency = null; + public TransactionBaseCurrency|string|null $currency = null; /** * The timestamp of when the transaction was created. @@ -52,16 +52,16 @@ class TransactionBase * - `FAILED`: The transaction attempt did not complete successfully. * - `REFUNDED`: The transaction was refunded in full or in part. * - * @var TransactionBaseStatus|null + * @var TransactionBaseStatus|string|null */ - public ?TransactionBaseStatus $status = null; + public TransactionBaseStatus|string|null $status = null; /** * Payment type used for the transaction. * - * @var TransactionBasePaymentType|null + * @var TransactionBasePaymentType|string|null */ - public ?TransactionBasePaymentType $paymentType = null; + public TransactionBasePaymentType|string|null $paymentType = null; /** * Number of installments for a deferred payment. diff --git a/src/Types/TransactionCheckoutInfo.php b/src/Types/TransactionCheckoutInfo.php index faa8993..26af386 100644 --- a/src/Types/TransactionCheckoutInfo.php +++ b/src/Types/TransactionCheckoutInfo.php @@ -33,9 +33,9 @@ class TransactionCheckoutInfo /** * Entry mode of the payment details. * - * @var TransactionCheckoutInfoEntryMode|null + * @var TransactionCheckoutInfoEntryMode|string|null */ - public ?TransactionCheckoutInfoEntryMode $entryMode = null; + public TransactionCheckoutInfoEntryMode|string|null $entryMode = null; /** * Authorization code for the transaction sent by the payment card issuer or bank. Applicable only to card payments. diff --git a/src/Types/TransactionEvent.php b/src/Types/TransactionEvent.php index 996075e..c28eea6 100644 --- a/src/Types/TransactionEvent.php +++ b/src/Types/TransactionEvent.php @@ -19,9 +19,9 @@ class TransactionEvent /** * Type of the transaction event. * - * @var TransactionEventEventType|null + * @var TransactionEventEventType|string|null */ - public ?TransactionEventEventType $eventType = null; + public TransactionEventEventType|string|null $eventType = null; /** * Status of the transaction event. @@ -34,9 +34,9 @@ class TransactionEvent * - `SUCCESSFUL`: The event completed successfully. Use this as the generic terminal success status for event types that do not expose a more specific business outcome such as `PAID_OUT` or `REFUNDED`. * - `FAILED`: The event could not be completed. Typical examples are a payout that could not be executed or an event that was rejected during processing. * - * @var TransactionEventStatus|null + * @var TransactionEventStatus|string|null */ - public ?TransactionEventStatus $status = null; + public TransactionEventStatus|string|null $status = null; /** * Amount of the event. diff --git a/src/Types/TransactionFull.php b/src/Types/TransactionFull.php index 3bce737..7505a17 100644 --- a/src/Types/TransactionFull.php +++ b/src/Types/TransactionFull.php @@ -199,16 +199,16 @@ class TransactionFull /** * Simple name of the payment type. * - * @var TransactionFullSimplePaymentType|null + * @var TransactionFullSimplePaymentType|string|null */ - public ?TransactionFullSimplePaymentType $simplePaymentType = null; + public TransactionFullSimplePaymentType|string|null $simplePaymentType = null; /** * Verification method used for the transaction. * - * @var TransactionFullVerificationMethod|null + * @var TransactionFullVerificationMethod|string|null */ - public ?TransactionFullVerificationMethod $verificationMethod = null; + public TransactionFullVerificationMethod|string|null $verificationMethod = null; /** * Details of the payment card. @@ -241,16 +241,16 @@ class TransactionFull /** * Payout type for the transaction. * - * @var TransactionFullPayoutType|null + * @var TransactionFullPayoutType|string|null */ - public ?TransactionFullPayoutType $payoutType = null; + public TransactionFullPayoutType|string|null $payoutType = null; /** * Whether the transaction was processed as credit or debit. * - * @var TransactionFullProcessAs|null + * @var TransactionFullProcessAs|string|null */ - public ?TransactionFullProcessAs $processAs = null; + public TransactionFullProcessAs|string|null $processAs = null; /** * List of products from the merchant's catalogue for which the transaction serves as a payment. @@ -286,9 +286,9 @@ class TransactionFull * - `CHARGEBACK`: The payment was subject to a chargeback. * - `NON_COLLECTION`: The amount could not be collected from the merchant after a chargeback or related adjustment. * - * @var TransactionFullSimpleStatus|null + * @var TransactionFullSimpleStatus|string|null */ - public ?TransactionFullSimpleStatus $simpleStatus = null; + public TransactionFullSimpleStatus|string|null $simpleStatus = null; /** * List of hyperlinks for accessing related resources. diff --git a/src/Types/TransactionHistory.php b/src/Types/TransactionHistory.php index 2ed780f..669e6f8 100644 --- a/src/Types/TransactionHistory.php +++ b/src/Types/TransactionHistory.php @@ -122,16 +122,16 @@ class TransactionHistory /** * Type of the transaction for the registered user specified in the `user` property. * - * @var TransactionHistoryType|null + * @var TransactionHistoryType|string|null */ - public ?TransactionHistoryType $type = null; + public TransactionHistoryType|string|null $type = null; /** * Issuing card network of the payment card used for the transaction. * - * @var TransactionHistoryCardType|null + * @var TransactionHistoryCardType|string|null */ - public ?TransactionHistoryCardType $cardType = null; + public TransactionHistoryCardType|string|null $cardType = null; /** * Payout date (if paid out at once). @@ -143,9 +143,9 @@ class TransactionHistory /** * Payout type. * - * @var TransactionHistoryPayoutType|null + * @var TransactionHistoryPayoutType|string|null */ - public ?TransactionHistoryPayoutType $payoutType = null; + public TransactionHistoryPayoutType|string|null $payoutType = null; /** * Total refunded amount. diff --git a/src/Types/TransactionMixinHistory.php b/src/Types/TransactionMixinHistory.php index 087540a..d333e0c 100644 --- a/src/Types/TransactionMixinHistory.php +++ b/src/Types/TransactionMixinHistory.php @@ -33,8 +33,8 @@ class TransactionMixinHistory /** * Payout plan of the registered user at the time when the transaction was made. * - * @var TransactionMixinHistoryPayoutPlan|null + * @var TransactionMixinHistoryPayoutPlan|string|null */ - public ?TransactionMixinHistoryPayoutPlan $payoutPlan = null; + public TransactionMixinHistoryPayoutPlan|string|null $payoutPlan = null; } diff --git a/src/Types/UnauthorizedErrors.php b/src/Types/UnauthorizedErrors.php index ccbbe15..c1ea36f 100644 --- a/src/Types/UnauthorizedErrors.php +++ b/src/Types/UnauthorizedErrors.php @@ -16,8 +16,8 @@ class UnauthorizedErrors /** * Key indicating type of error. Present only for typed 401 responses (e.g. invalid token, invalid password). Absent for generic unauthorized responses. * - * @var UnauthorizedErrorsType|null + * @var UnauthorizedErrorsType|string|null */ - public ?UnauthorizedErrorsType $type = null; + public UnauthorizedErrorsType|string|null $type = null; } diff --git a/tests/HydratorTest.php b/tests/HydratorTest.php index faea8e6..492d8e2 100644 --- a/tests/HydratorTest.php +++ b/tests/HydratorTest.php @@ -6,6 +6,7 @@ use SumUp\Hydrator; use SumUp\Types\Checkout; use SumUp\Types\CheckoutCurrency; +use SumUp\Types\CardResponseType; use SumUp\Types\MandateResponse; use SumUp\Types\MandateResponseStatus; use SumUp\Types\Receipt; @@ -79,13 +80,33 @@ public function testHydrateArrayItemsFromDocblockClassNames() $this->assertSame('https://example.test/2', $response->links[1]->href); } - public function testHydrateInvalidBackedEnumValueThrowsValueError() + public function testHydrateUnknownBackedEnumValuePreservesScalar() { - $this->expectException(\ValueError::class); - - Hydrator::hydrate([ - 'currency' => 'NOT_A_REAL_CURRENCY', + $checkout = Hydrator::hydrate([ + 'currency' => 'FUTURE_CURRENCY', ], Checkout::class); + + $this->assertSame('FUTURE_CURRENCY', $checkout->currency); + } + + public function testHydrateNullEnumProperty() + { + $checkout = Hydrator::hydrate(['currency' => null], Checkout::class); + + $this->assertNull($checkout->currency); + } + + public function testHydrateArrayOfOpenEnums() + { + $response = Hydrator::hydrate([ + 'types' => ['known' => 'VISA', 'instance' => CardResponseType::AMEX, 'new' => 'FUTURE_CARD'], + ], HydratorEnumArrayFixture::class); + + $this->assertSame([ + 'known' => CardResponseType::VISA, + 'instance' => CardResponseType::AMEX, + 'new' => 'FUTURE_CARD', + ], $response->types); } } @@ -104,3 +125,11 @@ class HydratorLinkFixture */ public ?string $href = null; } + +class HydratorEnumArrayFixture +{ + /** + * @var \SumUp\Types\CardResponseType[]|string[]|null + */ + public ?array $types = null; +} diff --git a/tests/RequestEncoderTest.php b/tests/RequestEncoderTest.php index c056e4c..dc75a43 100644 --- a/tests/RequestEncoderTest.php +++ b/tests/RequestEncoderTest.php @@ -43,6 +43,31 @@ public function testEncodeRecursivelyNormalizesNestedObjects() $this->assertSame('item-1', $encoded['items'][0]['item_id']); $this->assertSame('item-2', $encoded['items'][1]['item_id']); } + + public function testUnknownEnumValuesSurviveRequestConstructionAndEncoding() + { + $request = new CheckoutCreateRequest( + checkoutReference: 'order-123', + amount: 10.0, + currency: 'FUTURE_CURRENCY', + merchantCode: 'MC123', + purpose: 'FUTURE_PURPOSE', + ); + $fromArray = CheckoutCreateRequest::fromArray([ + 'checkout_reference' => 'order-123', + 'amount' => 10.0, + 'currency' => 'FUTURE_CURRENCY', + 'merchant_code' => 'MC123', + 'purpose' => 'FUTURE_PURPOSE', + ]); + + $this->assertSame('FUTURE_CURRENCY', $request->currency); + $this->assertSame('FUTURE_PURPOSE', $request->purpose); + $encoded = RequestEncoder::encode($request); + $this->assertSame('FUTURE_CURRENCY', $encoded['currency']); + $this->assertSame('FUTURE_PURPOSE', $encoded['purpose']); + $this->assertSame($encoded, RequestEncoder::encode($fromArray)); + } } class RequestEncoderFixture diff --git a/tests/SumUpTest.php b/tests/SumUpTest.php index 5b8cb4a..c8ddea5 100644 --- a/tests/SumUpTest.php +++ b/tests/SumUpTest.php @@ -140,6 +140,33 @@ public function testMethodAccessUsesDefaultToken() $this->assertInstanceOf(\SumUp\Services\Checkouts::class, $checkouts); } + /** + * @dataProvider unknownCardTypes + */ + public function testTransactionWithUnknownCardTypeCanBeRead(string $cardType) + { + $fakeClient = new FakeHttpClient(new Response(200, [ + 'id' => 'tx-1', + 'status' => 'SUCCESSFUL', + 'amount' => 2.0, + 'currency' => 'EUR', + 'card' => ['type' => $cardType, 'last_4_digits' => '1234'], + ])); + $sumup = new SumUp(['client' => $fakeClient, 'access_token' => 'token']); + + $transaction = $sumup->transactions()->get('MERCHANT'); + + $this->assertSame('tx-1', $transaction->id); + $this->assertSame('SUCCESSFUL', $transaction->status); + $this->assertSame($cardType, $transaction->card->type); + $this->assertSame('1234', $transaction->card->last4Digits); + } + + public static function unknownCardTypes(): array + { + return [['BANCOMAT'], ['SUMUP'], ['FUTURE_CARD']]; + } + public function testMagicPropertyAccessIsNotSupported() { $this->assertFalse(method_exists(SumUp::class, '__get'));