GooglePayRuby

A Ruby utility for securely decrypting Google Pay PaymentMethodTokens using the ECv2 protocol. This gem is inspired from Basis Theory google-pay-js library.

Features

  • Google Pay PaymentMethodToken Decryption: Securely decrypt user-authorized Google Pay transaction tokens using easy-to-use interfaces
  • Key Rotation Support: Handle multiple private keys simultaneously to support seamless key rotation without missing payments
  • Pure Ruby Implementation: No external dependencies beyond OpenSSL (included in Ruby standard library)
  • ECv2 Protocol Support: Implements Google Pay's ECv2 encryption protocol

Installation

Add this line to your application's Gemfile:

gem 'google_pay_ruby'

And then execute:

bundle install

Or install it yourself as:

gem install google_pay_ruby

Google Pay Setup

Follow Google Pay API guides for your platform. To use this library, you need to:

  1. Be PCI Level 1 certified
  2. Choose tokenization type DIRECT
  3. Generate and register your encryption keys with Google

⚠️ Important: If you are not PCI Level 1 certified, consider using PAYMENT_GATEWAY tokenization type or contact a payment gateway provider.

Usage

Basic Example

require 'google_pay_ruby'

# Load your private key (from file, KMS, secrets manager, etc.)
private_key_pem = File.read('path/to/your/private_key.pem')

# Create the decryption context
context = GooglePayRuby::GooglePaymentMethodTokenContext.new(
  merchants: [
    {
      identifier: 'my-merchant-identifier',  # Optional, for debugging
      private_key_pem: private_key_pem
    }
  ]
)

# Get the token from Google Pay API response
token = {
  'protocolVersion' => 'ECv2',
  'signature' => '...',
  'signedMessage' => '{"encryptedMessage":"...","ephemeralPublicKey":"...","tag":"..."}'
}

# Decrypt the token
begin
  decrypted_data = context.decrypt(token)

  puts "PAN: #{decrypted_data['paymentMethodDetails']['pan']}"
  puts "Expiration: #{decrypted_data['paymentMethodDetails']['expirationMonth']}/#{decrypted_data['paymentMethodDetails']['expirationYear']}"
  puts "Cryptogram: #{decrypted_data['paymentMethodDetails']['cryptogram']}"
rescue GooglePayRuby::GooglePaymentDecryptionError => e
  puts "Decryption failed: #{e.message}"
end

Key Rotation Support

Handle key rotation gracefully by providing multiple private keys:

context = GooglePayRuby::GooglePaymentMethodTokenContext.new(
  merchants: [
    {
      identifier: 'current-key',
      private_key_pem: File.read('current_key.pem')
    },
    {
      identifier: 'previous-key',
      private_key_pem: File.read('previous_key.pem')
    }
  ]
)

# The library will try each key until decryption succeeds
decrypted_data = context.decrypt(token)

Decrypted Data Structure

The decrypted payment data contains:

{
  "gatewayMerchantId" => "your-gateway-merchant-id",
  "messageExpiration" => "1234567890123",
  "messageId" => "AH2Ejtc...",
  "paymentMethod" => "CARD",
  "paymentMethodDetails" => {
    "pan" => "4111111111111111",
    "expirationMonth" => 12,
    "expirationYear" => 2025,
    "authMethod" => "CRYPTOGRAM_3DS",
    "cryptogram" => "AAAAAA...",
    "eciIndicator" => "05"
  }
}

Error Handling

The gem raises GooglePayRuby::GooglePaymentDecryptionError when decryption fails:

begin
  decrypted_data = context.decrypt(token)
rescue GooglePayRuby::GooglePaymentDecryptionError => e
  # Access detailed error information
  puts e.message
  puts e.full_message  # Includes details from all attempted keys

  # Access individual errors
  e.errors.each do |error|
    puts "Merchant: #{error.merchant_identifier}"
    puts "Error: #{error.message}"
  end
end

Development

After checking out the repo, run bundle install to install dependencies. Then, run ruby test/test_decrypt.rb to run the tests.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/better-payment/google-pay-ruby.

License

The gem is available as open source under the terms of the MIT License.

Credits

This Ruby implementation is inspired from Basis Theory google-pay-js library.