Skip to content
Draft
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions codegen/pkg/generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(")
Expand All @@ -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 {
Expand Down Expand Up @@ -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"
Expand All @@ -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"
Expand Down
16 changes: 2 additions & 14 deletions codegen/pkg/generator/properties.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
52 changes: 50 additions & 2 deletions codegen/pkg/generator/properties_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
}
19 changes: 15 additions & 4 deletions src/Hydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use ReflectionClass;
use ReflectionNamedType;
use ReflectionProperty;
use ReflectionUnionType;

/**
* Hydrates SDK models from associative arrays or stdClass payloads.
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -146,10 +157,6 @@ private static function castEnumValue($value, $enumClass)
}
}

if (method_exists($enumClass, 'from')) {
return $enumClass::from($value);
}

return $value;
}

Expand Down Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Types/BadRequestErrors.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class BadRequestErrors
/**
* Key indicating type of error
*
* @var BadRequestErrorsType
* @var BadRequestErrorsType|string
*/
public BadRequestErrorsType $type;
public BadRequestErrorsType|string $type;

}
4 changes: 2 additions & 2 deletions src/Types/Card.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

}
4 changes: 2 additions & 2 deletions src/Types/CardResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

}
8 changes: 4 additions & 4 deletions src/Types/Checkout.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions src/Types/CheckoutCreateRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/Types/CheckoutUpdateRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/Types/CreateReaderCheckoutRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/Types/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions src/Types/FinancialPayout.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
12 changes: 6 additions & 6 deletions src/Types/GetReaderCheckoutResponseData.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/Types/MandatePayload.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/Types/MandateResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading
Loading