Class: GooglePayRuby::GooglePaymentMethodTokenContext

Inherits:
Object
  • Object
show all
Defined in:
lib/google_pay_ruby/google_payment_method_token_context.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ GooglePaymentMethodTokenContext

Returns a new instance of GooglePaymentMethodTokenContext.

Parameters:

  • options (Hash)

Options Hash (options):

  • :merchants (Array<Hash>) —

    List of merchant configs with :private_key_pem and optional :identifier

  • :recipient_id (String) —

    The recipient ID for message signature verification (e.g. "merchant:" or "gateway:"). Required when verify_signature is true.

  • :root_signing_keys (Array<Hash>, nil) —

    Pre-fetched Google root signing keys (ECv2 only). If nil, fetched automatically from Google's public URL.

  • :test (Boolean) —

    Whether to use Google's test keys URL (default: false)

  • :verify_signature (Boolean) —

    Whether to verify token signatures before decryption (default: true)

  • :verify_expiration (Boolean) —

    Whether to verify messageExpiration after decryption (default: true)

  • :gateway_merchant_id (String, nil) —

    Expected gatewayMerchantId to verify in the decrypted payload. When provided, the decrypted paymentMethodDetails.gatewayMerchantId must match this value.

  • :verify_merchant_id (Boolean) —

    Whether to verify gatewayMerchantId after decryption (default: true if gateway_merchant_id is provided)



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/google_pay_ruby/google_payment_method_token_context.rb', line 19

def initialize(options)
  @merchants = options[:merchants] || []
  @recipient_id = options[:recipient_id]
  @root_signing_keys = options[:root_signing_keys]
  @gateway_merchant_id = options[:gateway_merchant_id]
  @test = options.fetch(:test, false)
  @verify_signature = options.fetch(:verify_signature, true)
  @verify_expiration = options.fetch(:verify_expiration, true)
  @verify_merchant_id = options.fetch(:verify_merchant_id, !@gateway_merchant_id.nil?)

  if @merchants.empty?
    raise GooglePaymentDecryptionError.new(
      'No merchant configuration provided for decryption context.'
    )
  end

  if @verify_signature && (@recipient_id.nil? || @recipient_id.empty?)
    raise ArgumentError, ':recipient_id is required when signature verification is enabled'
  end

  validate_merchant_configurations!
end

Instance Attribute Details

#merchants ⇒ Object (readonly)

Returns the value of attribute merchants.



5
6
7
# File 'lib/google_pay_ruby/google_payment_method_token_context.rb', line 5

def merchants
  @merchants
end

Instance Method Details

#decrypt(token) ⇒ Object

Parameters:

  • token (Hash, String) —

    The Google Pay payment method token. Can be a Hash (already parsed) or a JSON String (will be parsed internally). When a String is provided, the raw JSON is preserved for signature verification to handle unicode escapes (e.g. \u003d) that JSON.parse would decode.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/google_pay_ruby/google_payment_method_token_context.rb', line 46

def decrypt(token)
  if token.is_a?(String)
    raw_token_json = token
    token = JSON.parse(token)
  end

  protocol_version = token['protocolVersion'] || token[:protocolVersion]
  unless protocol_version == 'ECv2'
    raise GooglePaymentDecryptionError.new(
      "Unsupported decryption for protocol version #{protocol_version}"
    )
  end

  # Steps 1-4: Verify signatures before decryption
  if @verify_signature
    verifier = SignatureVerifier.new(
      root_signing_keys: @root_signing_keys,
      recipient_id: @recipient_id,
      test: @test
    )
    verifier.verify!(token, raw_token_json: raw_token_json)
  end

  # Step 5: Decrypt the payload
  errors = []
  signed_message = token['signedMessage'] || token[:signedMessage]

  decrypted_data = nil
  @merchants.each do |merchant|
    begin
      strategy = EcV2DecryptionStrategy.new(merchant[:private_key_pem])
      decrypted_data = strategy.decrypt(signed_message)
      break
    rescue StandardError => e
      e.define_singleton_method(:merchant_identifier) { merchant[:identifier] }
      errors << e
    end
  end

  unless decrypted_data
    raise GooglePaymentDecryptionError.new(
      'Failed to decrypt payment data using provided merchant configuration(s).',
      errors
    )
  end

  # Step 6: Verify message hasn't expired
  if @verify_expiration
    verify_message_expiration!(decrypted_data)
  end

  # Step 7: Verify gatewayMerchantId matches expected value
  if @verify_merchant_id
    verify_gateway_merchant_id!(decrypted_data)
  end

  decrypted_data
end