Skip to content
This repository was archived by the owner on Oct 16, 2025. It is now read-only.

Commit b776c13

Browse files
committed
fix: add flexible timestamp decoding for DiscountOfferInputIOS
- Add custom Decodable implementation to handle String -> Double conversion - Fixes type mismatch when timestamp is sent as String from JS/TS bridges - Maintains backward compatibility with numeric timestamps
1 parent 29a9da1 commit b776c13

File tree

3 files changed

+93
-13
lines changed

3 files changed

+93
-13
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openiap-gql",
3-
"version": "1.2.0",
3+
"version": "1.2.1",
44
"type": "module",
55
"main": "index.js",
66
"scripts": {

scripts/generate-swift-types.mjs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,62 @@ const printInput = (inputType) => {
366366
lines.push('');
367367
return;
368368
}
369+
// Custom Decodable for DiscountOfferInputIOS to handle String -> Double conversion
370+
if (inputType.name === 'DiscountOfferInputIOS') {
371+
addDocComment(lines, inputType.description);
372+
lines.push('public struct DiscountOfferInputIOS: Codable {');
373+
lines.push(' public var identifier: String');
374+
lines.push(' public var keyIdentifier: String');
375+
lines.push(' public var nonce: String');
376+
lines.push(' public var signature: String');
377+
lines.push(' public var timestamp: Double');
378+
lines.push('');
379+
lines.push(' public init(identifier: String, keyIdentifier: String, nonce: String, signature: String, timestamp: Double) {');
380+
lines.push(' self.identifier = identifier');
381+
lines.push(' self.keyIdentifier = keyIdentifier');
382+
lines.push(' self.nonce = nonce');
383+
lines.push(' self.signature = signature');
384+
lines.push(' self.timestamp = timestamp');
385+
lines.push(' }');
386+
lines.push('');
387+
lines.push(' private enum CodingKeys: String, CodingKey {');
388+
lines.push(' case identifier, keyIdentifier, nonce, signature, timestamp');
389+
lines.push(' }');
390+
lines.push('');
391+
lines.push(' public init(from decoder: Decoder) throws {');
392+
lines.push(' let container = try decoder.container(keyedBy: CodingKeys.self)');
393+
lines.push(' identifier = try container.decode(String.self, forKey: .identifier)');
394+
lines.push(' keyIdentifier = try container.decode(String.self, forKey: .keyIdentifier)');
395+
lines.push(' nonce = try container.decode(String.self, forKey: .nonce)');
396+
lines.push(' signature = try container.decode(String.self, forKey: .signature)');
397+
lines.push('');
398+
lines.push(' // Flexible timestamp decoding: accept Double or String');
399+
lines.push(' if let timestampDouble = try? container.decode(Double.self, forKey: .timestamp) {');
400+
lines.push(' timestamp = timestampDouble');
401+
lines.push(' } else if let timestampString = try? container.decode(String.self, forKey: .timestamp),');
402+
lines.push(' let timestampDouble = Double(timestampString) {');
403+
lines.push(' timestamp = timestampDouble');
404+
lines.push(' } else {');
405+
lines.push(' throw DecodingError.dataCorruptedError(');
406+
lines.push(' forKey: .timestamp,');
407+
lines.push(' in: container,');
408+
lines.push(' debugDescription: "timestamp must be a number or numeric string"');
409+
lines.push(' )');
410+
lines.push(' }');
411+
lines.push(' }');
412+
lines.push('');
413+
lines.push(' public func encode(to encoder: Encoder) throws {');
414+
lines.push(' var container = encoder.container(keyedBy: CodingKeys.self)');
415+
lines.push(' try container.encode(identifier, forKey: .identifier)');
416+
lines.push(' try container.encode(keyIdentifier, forKey: .keyIdentifier)');
417+
lines.push(' try container.encode(nonce, forKey: .nonce)');
418+
lines.push(' try container.encode(signature, forKey: .signature)');
419+
lines.push(' try container.encode(timestamp, forKey: .timestamp)');
420+
lines.push(' }');
421+
lines.push('}');
422+
lines.push('');
423+
return;
424+
}
369425
if (inputType.name === 'RequestPurchaseProps') {
370426
addDocComment(lines, inputType.description);
371427
lines.push('public struct RequestPurchaseProps: Codable {');

src/generated/Types.swift

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -567,30 +567,54 @@ public struct DeepLinkOptions: Codable {
567567
}
568568

569569
public struct DiscountOfferInputIOS: Codable {
570-
/// Discount identifier
571570
public var identifier: String
572-
/// Key identifier for validation
573571
public var keyIdentifier: String
574-
/// Cryptographic nonce
575572
public var nonce: String
576-
/// Signature for validation
577573
public var signature: String
578-
/// Timestamp of discount offer
579574
public var timestamp: Double
580575

581-
public init(
582-
identifier: String,
583-
keyIdentifier: String,
584-
nonce: String,
585-
signature: String,
586-
timestamp: Double
587-
) {
576+
public init(identifier: String, keyIdentifier: String, nonce: String, signature: String, timestamp: Double) {
588577
self.identifier = identifier
589578
self.keyIdentifier = keyIdentifier
590579
self.nonce = nonce
591580
self.signature = signature
592581
self.timestamp = timestamp
593582
}
583+
584+
private enum CodingKeys: String, CodingKey {
585+
case identifier, keyIdentifier, nonce, signature, timestamp
586+
}
587+
588+
public init(from decoder: Decoder) throws {
589+
let container = try decoder.container(keyedBy: CodingKeys.self)
590+
identifier = try container.decode(String.self, forKey: .identifier)
591+
keyIdentifier = try container.decode(String.self, forKey: .keyIdentifier)
592+
nonce = try container.decode(String.self, forKey: .nonce)
593+
signature = try container.decode(String.self, forKey: .signature)
594+
595+
// Flexible timestamp decoding: accept Double or String
596+
if let timestampDouble = try? container.decode(Double.self, forKey: .timestamp) {
597+
timestamp = timestampDouble
598+
} else if let timestampString = try? container.decode(String.self, forKey: .timestamp),
599+
let timestampDouble = Double(timestampString) {
600+
timestamp = timestampDouble
601+
} else {
602+
throw DecodingError.dataCorruptedError(
603+
forKey: .timestamp,
604+
in: container,
605+
debugDescription: "timestamp must be a number or numeric string"
606+
)
607+
}
608+
}
609+
610+
public func encode(to encoder: Encoder) throws {
611+
var container = encoder.container(keyedBy: CodingKeys.self)
612+
try container.encode(identifier, forKey: .identifier)
613+
try container.encode(keyIdentifier, forKey: .keyIdentifier)
614+
try container.encode(nonce, forKey: .nonce)
615+
try container.encode(signature, forKey: .signature)
616+
try container.encode(timestamp, forKey: .timestamp)
617+
}
594618
}
595619

596620
/// Connection initialization configuration

0 commit comments

Comments
 (0)