wallet_passkit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2ca6dbbeb4df4946ae410fadb3c040d8770e9a9ba1db838d90d151e805d98ab0
4
+ data.tar.gz: 83efa527ed456b51131a8a0bcc15c870bb618a4837bebbd2c4dde72cb089e32d
5
+ SHA512:
6
+ metadata.gz: a436ab2fe7597f2740b58eb996090641ee66134b22d0bf64829928696a5bf76442dd8a22e90a701545f6a41ef36505347753b5d71a4db988c8601a74f3b9fa54
7
+ data.tar.gz: e6c5eb667e70f6e638e449b6c50bad4c22d1a8d60e2020d682d9066e780e89e6ace194076d174841848471a3cad61ff0d908d6413f1574b1bbfac908cfd52a90
data/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # wallet_passkit
2
+
3
+ A modern Ruby gem to generate Apple Wallet passes (.pkpass) and integrate easily into Rails projects. Also includes Google Wallet helpers (Save link, object updates).
4
+
5
+ [![CI](https://github.com/gioggi/wallet_passkit/actions/workflows/test.yml/badge.svg)](https://github.com/gioggi/wallet_passkit/actions/workflows/test.yml)
6
+
7
+ Status: initial implementation for Apple + Google (Save link + updater).
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'wallet_passkit', path: '.' # or gem 'wallet_passkit', version: '0.1.0'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ ```bash
20
+ bundle install
21
+ ```
22
+
23
+ ## Configuration
24
+
25
+ Global configuration in plain Ruby:
26
+
27
+ ```ruby
28
+ require 'wallet_passkit'
29
+
30
+ WalletPasskit.configure do |c|
31
+ c.apple_pass_certificate_p12_path = "/path/to/pass_certificate.p12"
32
+ c.apple_pass_certificate_password = ENV["APPLE_PASS_P12_PASSWORD"]
33
+ c.apple_wwdr_certificate_path = "/path/to/AppleWWDRCAG3.pem" # download from Apple
34
+ c.apple_team_identifier = "TEAMID1234"
35
+ c.apple_organization_name = "My Org"
36
+
37
+ # Google Wallet (optional for Save link / updater)
38
+ c.google_service_account_credentials = "/path/to/service_account.json"
39
+ c.google_issuer_id = "issuer-id" # optional
40
+ c.google_class_prefix = "com.example" # optional
41
+ end
42
+ ```
43
+
44
+ Rails (config/initializers/wallet_passkit.rb):
45
+
46
+ ```ruby
47
+ Rails.application.config.wallet_passkit.apple_pass_certificate_p12_path = Rails.root.join('config', 'certs', 'pass_cert.p12').to_s
48
+ Rails.application.config.wallet_passkit.apple_pass_certificate_password = ENV['APPLE_PASS_P12_PASSWORD']
49
+ Rails.application.config.wallet_passkit.apple_wwdr_certificate_path = Rails.root.join('config', 'certs', 'AppleWWDRCAG3.pem').to_s
50
+ Rails.application.config.wallet_passkit.apple_team_identifier = ENV['APPLE_TEAM_ID']
51
+ Rails.application.config.wallet_passkit.apple_organization_name = 'My Org'
52
+
53
+ Rails.application.config.wallet_passkit.google_service_account_credentials = Rails.root.join('config', 'google', 'service_account.json').to_s
54
+ ```
55
+
56
+ ## Apple Wallet: Generate a .pkpass
57
+
58
+ At minimum you need:
59
+ - A pass type ID certificate (.p12) and password
60
+ - Apple WWDR certificate (PEM)
61
+ - A `pass.json` payload
62
+ - Required images (icon.png; icon@2x.png recommended)
63
+
64
+ Example in Rails controller action:
65
+
66
+ ```ruby
67
+ payload = WalletPasskit::Apple::Service.build_pass_payload(
68
+ description: 'Loyalty Card',
69
+ pass_type_identifier: 'pass.com.example.loyalty',
70
+ serial_number: 'ABC123',
71
+ logo_text: 'My Store',
72
+ primary_fields: [ { key: 'points', label: 'Points', value: '100' } ],
73
+ background_color: 'rgb(255,0,0)',
74
+ foreground_color: 'rgb(255,255,255)'
75
+ )
76
+
77
+ assets = {
78
+ 'icon.png' => File.binread(Rails.root.join('app/assets/images/pass/icon.png')),
79
+ 'icon@2x.png' => File.binread(Rails.root.join('app/assets/images/pass/icon@2x.png')),
80
+ # add logo.png, strip.png, background.png as needed
81
+ }
82
+
83
+ pkpass_binary = WalletPasskit::Apple::Service.generate_pkpass(pass_payload: payload, assets: assets)
84
+
85
+ send_data pkpass_binary, filename: 'loyalty.pkpass', type: 'application/vnd.apple.pkpass'
86
+ ```
87
+
88
+ If you prefer to fully control pass.json, just pass your own hash to `generate_pkpass`.
89
+
90
+ ## Google Wallet
91
+
92
+ For step-by-step setup (Issuer, API enablement, Service Account, LoyaltyClass) and usage examples (Save link and updates), see the dedicated guide:
93
+ - https://github.com/gioggi/wallet_passkit/blob/main/lib/wallet_passkit/google/README.md
94
+
95
+ This gem generates a Save to Google Wallet link (JWT) and provides a minimal updater for loyalty points.
96
+
97
+ ## Development
98
+
99
+ - Run tests: `bundle exec rspec`
100
+ - Lint: pending
101
+
102
+ ## Notes & Limitations
103
+
104
+ - Apple requires asset files to be included and a correctly signed manifest. This gem signs with PKCS#7 (DER) using your pass certificate and Apple WWDR intermediate.
105
+ - You must handle obtaining and managing certificates/keys securely.
106
+ - For Google Wallet, this gem builds a Save link JWT and provides a minimal updater; for full class/object management, extend accordingly.
107
+
108
+ ## License
109
+
110
+ MIT
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "json"
5
+ require "digest/sha1"
6
+ require "zip"
7
+
8
+ module WalletPasskit
9
+ module Apple
10
+ class Service
11
+ def self.build_pass_payload(description:, pass_type_identifier:, serial_number:, logo_text:, primary_fields: [], secondary_fields: [], auxiliary_fields: [], back_fields: [], team_identifier: nil, organization_name: nil, pass_type: :storeCard, background_color: nil, label_color: nil, foreground_color: nil)
12
+ team_identifier ||= WalletPasskit.config.apple_team_identifier
13
+ organization_name ||= WalletPasskit.config.apple_organization_name
14
+
15
+ base = {
16
+ formatVersion: 1,
17
+ description: description,
18
+ organizationName: organization_name,
19
+ teamIdentifier: team_identifier,
20
+ passTypeIdentifier: pass_type_identifier,
21
+ serialNumber: serial_number,
22
+ logoText: logo_text
23
+ }
24
+
25
+ section_key = pass_type.to_sym == :storeCard ? :storeCard : :generic
26
+ section_payload = {}
27
+ section_payload[:primaryFields] = primary_fields if primary_fields && !primary_fields.empty?
28
+ section_payload[:secondaryFields] = secondary_fields if secondary_fields && !secondary_fields.empty?
29
+ section_payload[:auxiliaryFields] = auxiliary_fields if auxiliary_fields && !auxiliary_fields.empty?
30
+ section_payload[:backFields] = back_fields if back_fields && !back_fields.empty?
31
+
32
+ base[section_key] = section_payload unless section_payload.empty?
33
+
34
+ base[:backgroundColor] = background_color if background_color
35
+ base[:labelColor] = label_color if label_color
36
+ base[:foregroundColor] = foreground_color if foreground_color
37
+
38
+ base
39
+ end
40
+
41
+ def self.generate_pkpass(pass_payload:, assets: {})
42
+ p12_path = WalletPasskit.config.apple_pass_certificate_p12_path
43
+ p12_password = WalletPasskit.config.apple_pass_certificate_password
44
+ wwdr_path = WalletPasskit.config.apple_wwdr_certificate_path
45
+
46
+ raise WalletPasskit::Error, "Missing Apple certificate configuration" unless p12_path && p12_password && wwdr_path
47
+
48
+ files = { "pass.json" => JSON.pretty_generate(pass_payload) }
49
+ files.merge!(assets)
50
+
51
+ manifest = {}
52
+ files.each do |name, content|
53
+ manifest[name] = Digest::SHA1.hexdigest(content)
54
+ end
55
+ manifest_json = JSON.generate(manifest)
56
+
57
+ signature_der = WalletPasskit::Apple::Signer.sign(
58
+ data: manifest_json,
59
+ p12_path: p12_path,
60
+ p12_password: p12_password,
61
+ wwdr_path: wwdr_path
62
+ )
63
+
64
+ io = StringIO.new
65
+ Zip::OutputStream.write_buffer(io) do |zip|
66
+ files.each do |name, content|
67
+ zip.put_next_entry(name)
68
+ zip.write(content)
69
+ end
70
+
71
+ zip.put_next_entry("manifest.json")
72
+ zip.write(manifest_json)
73
+
74
+ zip.put_next_entry("signature")
75
+ zip.write(signature_der)
76
+ end
77
+ io.rewind
78
+ io.read
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module WalletPasskit
6
+ module Apple
7
+ class Signer
8
+ def self.sign(data:, p12_path:, p12_password:, wwdr_path:)
9
+ p12 = OpenSSL::PKCS12.new(File.binread(p12_path), p12_password)
10
+ key = p12.key
11
+ cert = p12.certificate
12
+ wwdr = OpenSSL::X509::Certificate.new(File.read(wwdr_path))
13
+
14
+ store = OpenSSL::X509::Store.new
15
+ store.add_cert(wwdr) rescue nil
16
+
17
+ flags = OpenSSL::PKCS7::BINARY | OpenSSL::PKCS7::DETACHED
18
+ pkcs7 = OpenSSL::PKCS7.sign(cert, key, data, [wwdr], flags)
19
+ pkcs7.to_der
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "apple/service"
4
+ require_relative "apple/signer"
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WalletPasskit
4
+ # Global configuration container
5
+ class Configuration
6
+ # Apple Wallet certs configuration
7
+ attr_accessor :apple_pass_certificate_p12_path, :apple_pass_certificate_password,
8
+ :apple_wwdr_certificate_path, :apple_team_identifier, :apple_organization_name
9
+
10
+ # Google Wallet configuration (service account credentials JSON path or hash)
11
+ attr_accessor :google_service_account_credentials, :google_issuer_id, :google_class_prefix
12
+
13
+ def initialize
14
+ @apple_pass_certificate_p12_path = nil
15
+ @apple_pass_certificate_password = nil
16
+ @apple_wwdr_certificate_path = nil
17
+ @apple_team_identifier = nil
18
+ @apple_organization_name = nil
19
+
20
+ @google_service_account_credentials = nil # path or parsed hash
21
+ @google_issuer_id = nil
22
+ @google_class_prefix = nil
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module WalletPasskit
2
+ class Error < StandardError; end
3
+ end
@@ -0,0 +1,136 @@
1
+ # WalletPasskit – Google Wallet Guide
2
+
3
+ Helpers to create Save to Google Wallet links and update Loyalty cards using Ruby.
4
+
5
+ ### What you can do
6
+ - Generate a "Save to Google Wallet" link (JWT)
7
+ - Customize brand colors, logo, hero image, and store locations
8
+ - Assign a QR/barcode and dynamic loyalty points
9
+ - Update points later via REST (PATCH)
10
+
11
+ ---
12
+
13
+ ## Prerequisites
14
+ - A Google Wallet Issuer account for your business
15
+ - A Google Cloud project with the Google Wallet API enabled
16
+ - A Service Account with a JSON key file
17
+
18
+ ---
19
+
20
+ ## Step-by-step setup
21
+
22
+ ### 1) Request/Configure your Issuer
23
+ - Open the Google Pay & Wallet Console: `https://pay.google.com/business/console`
24
+ - Request an Issuer account if you do not have one yet and complete verification (business info, branding, domain if applicable)
25
+ - Note your Issuer ID (a long numeric id, e.g. `3388000000000000000`)
26
+
27
+ ### 2) Enable the API in Google Cloud
28
+ - Go to `https://console.cloud.google.com` and select or create a project
29
+ - Enable "Google Wallet API" from the API Library
30
+
31
+ ### 3) Create a Service Account + key
32
+ - In Google Cloud Console: IAM & Admin → Service Accounts → Create
33
+ - Grant a role that can access Wallet Objects (Editor is sufficient for development)
34
+ - Create a JSON key and download it
35
+ - Store it in your app, e.g. `config/google/service_account.json`
36
+
37
+ ### 4) Grant the Service Account access to the Issuer
38
+ - In the Google Pay & Wallet Console, add the service account email as a member/collaborator for your Issuer so it can create/update objects
39
+
40
+ ### 5) Create a LoyaltyClass
41
+ Your passes (objects) must reference an existing LoyaltyClass.
42
+
43
+ You can create it via the Console (if available) or via REST API. The class id format is:
44
+
45
+ - `loyaltyClass.id = "<ISSUER_ID>.<CLASS_ID>"` (e.g., `3388000000000000000.keristo_loyalty`)
46
+
47
+ Minimal LoyaltyClass via REST:
48
+
49
+ ```bash
50
+ curl -X POST \
51
+ -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
52
+ -H "Content-Type: application/json" \
53
+ -d '{
54
+ "id": "3388000000000000000.keristo_loyalty",
55
+ "issuerName": "My Brand",
56
+ "programName": "Keristo Loyalty",
57
+ "reviewStatus": "underReview"
58
+ }' \
59
+ https://walletobjects.googleapis.com/walletobjects/v1/loyaltyClass
60
+ ```
61
+
62
+ You can add branding fields later (logo, colors, etc.) directly on the class or customize per-object.
63
+
64
+ ---
65
+
66
+ ## Configuration in your app
67
+ Add the service account path to your configuration. For Rails:
68
+
69
+ ```ruby
70
+ Rails.application.config.wallet_passkit.google_service_account_credentials = \
71
+ Rails.root.join("config", "google", "service_account.json").to_s
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Generate a Save to Google Wallet link
77
+ Build your company and customer hashes, then create the URL.
78
+
79
+ ```ruby
80
+ company = {
81
+ issuer_id: ENV["GOOGLE_ISSUER_ID"], # e.g. "3388000000000000000"
82
+ class_id: "keristo_loyalty", # your LoyaltyClass suffix
83
+ background_color: "#112233", # optional
84
+ font_color: "#FFFFFF", # optional
85
+ logo_uri: "https://example.com/logo.png", # optional
86
+ hero_image_uri: "https://example.com/hero.png", # optional
87
+ locations: [ { latitude: 45.4642, longitude: 9.1900 } ] # optional
88
+ }
89
+
90
+ customer = {
91
+ id: "customer-123",
92
+ first_name: "Mario",
93
+ last_name: "Rossi",
94
+ points: 120,
95
+ qr_value: "UUID-1234" # or use :barcode_value and optional :barcode_type
96
+ }
97
+
98
+ url = WalletPasskit::Google::Pass.new(
99
+ company: company,
100
+ customer: customer,
101
+ service_account_path: Rails.application.config.wallet_passkit.google_service_account_credentials
102
+ ).save_url
103
+ # Redirect the user to `url`, or render it as a link/button
104
+ ```
105
+
106
+ What the gem generates
107
+ - A JWT with `payload.loyaltyObjects[0]` that includes your colors, images, locations, and the customer’s points/barcode
108
+ - The Save URL: `https://pay.google.com/gp/v/save/<JWT>`
109
+
110
+ ---
111
+
112
+ ## Update points later (PATCH)
113
+ Use the object id format: `<ISSUER_ID>.<CUSTOMER_ID>`.
114
+
115
+ ```ruby
116
+ object_id = "#{company[:issuer_id]}.#{customer[:id]}"
117
+
118
+ WalletPasskit::Google::PassUpdater.new(
119
+ object_id: object_id,
120
+ service_account_path: Rails.application.config.wallet_passkit.google_service_account_credentials
121
+ ).update_points(new_point_value: 480)
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Troubleshooting
127
+ - 401/403 errors: ensure the Service Account is added to your Issuer in the Wallet Console and the API is enabled
128
+ - Not found: verify the LoyaltyClass exists and your object references `classId = "<ISSUER_ID>.<CLASS_ID>"`
129
+ - Branding not visible: check whether you set colors/images on the class vs object; some UI elements derive from the class
130
+
131
+ ---
132
+
133
+ ## Useful links
134
+ - Developer site: `https://developers.google.com/wallet`
135
+ - Loyalty cards API reference: `https://developers.google.com/wallet/retail/loyalty-cards`
136
+
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "googleauth"
4
+
5
+ module WalletPasskit
6
+ module Google
7
+ module Auth
8
+ def self.access_token(service_account_path:)
9
+ credentials = ::Google::Auth::ServiceAccountCredentials.make_creds(
10
+ json_key_io: File.open(service_account_path),
11
+ scope: ["https://www.googleapis.com/auth/wallet_object.issuer"]
12
+ )
13
+ credentials.fetch_access_token!
14
+ credentials.access_token
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "googleauth"
4
+ require "jwt"
5
+ require "net/http"
6
+ require "uri"
7
+ require "json"
8
+
9
+ module WalletPasskit
10
+ module Google
11
+ class Pass
12
+ def initialize(company:, customer:, service_account_path:)
13
+ @company = company.transform_keys(&:to_sym)
14
+ @customer = customer.transform_keys(&:to_sym)
15
+ @service_account_path = service_account_path
16
+ end
17
+
18
+ def save_url
19
+ # For save URL, we need a simplified object structure
20
+ # The full object structure is only for API calls
21
+ save_object = {
22
+ id: "#{issuer_id}.#{@customer[:id]}",
23
+ classId: "#{issuer_id}.#{@company[:class_id]}",
24
+ state: "active",
25
+ accountId: @customer[:id],
26
+ accountName: "#{@customer[:first_name]} #{@customer[:last_name]}",
27
+ loyaltyPoints: {
28
+ label: "Punti",
29
+ balance: {
30
+ int: @customer[:points] || 0
31
+ }
32
+ }
33
+ }
34
+
35
+ # Add optional fields for save URL
36
+ if @company[:background_color]
37
+ save_object[:hexBackgroundColor] = @company[:background_color]
38
+ end
39
+
40
+ if @company[:font_color]
41
+ save_object[:hexFontColor] = @company[:font_color]
42
+ end
43
+
44
+ # Add barcode for save URL
45
+ if @customer[:qr_value]
46
+ save_object[:barcode] = {
47
+ type: "QR_CODE",
48
+ value: @customer[:qr_value]
49
+ }
50
+ elsif @customer[:barcode_value]
51
+ save_object[:barcode] = {
52
+ type: (@customer[:barcode_type] || "QR_CODE"),
53
+ value: @customer[:barcode_value]
54
+ }
55
+ end
56
+
57
+ payload = {
58
+ iss: service_account_email,
59
+ aud: "google",
60
+ typ: "savetowallet",
61
+ payload: {
62
+ loyaltyObjects: [save_object]
63
+ }
64
+ }
65
+
66
+ jwt = JWT.encode(payload, private_key, "RS256", kid: key_id)
67
+ "https://pay.google.com/gp/v/save/#{jwt}"
68
+ end
69
+
70
+ # Creates a loyalty object in Google Wallet using the API
71
+ def create_loyalty_object
72
+ loyalty_object_data = build_loyalty_object
73
+
74
+ uri = URI("https://walletobjects.googleapis.com/walletobjects/v1/loyaltyObject")
75
+ req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
76
+ req["Authorization"] = "Bearer #{access_token}"
77
+ req.body = loyalty_object_data.to_json
78
+
79
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
80
+ http.request(req)
81
+ end
82
+
83
+ if res.is_a?(Net::HTTPSuccess)
84
+ created_object = JSON.parse(res.body)
85
+ puts "✅ Loyalty object created successfully!"
86
+ puts " ID: #{created_object['id']}"
87
+ puts " State: #{created_object['state']}"
88
+ created_object
89
+ else
90
+ error_message = "Failed to create loyalty object: #{res.code} #{res.message}"
91
+ if res.body
92
+ error_data = JSON.parse(res.body) rescue nil
93
+ if error_data && error_data['error']
94
+ error_message += " - #{error_data['error']['message']}"
95
+ end
96
+ end
97
+ raise WalletPasskit::Error, error_message
98
+ end
99
+ end
100
+
101
+ # Creates loyalty object and returns the save URL
102
+ def create_and_save_url
103
+ create_loyalty_object
104
+ save_url
105
+ end
106
+
107
+ private
108
+
109
+ def access_token
110
+ WalletPasskit::Google::Auth.access_token(service_account_path: @service_account_path)
111
+ end
112
+
113
+ def service_account_email
114
+ JSON.parse(File.read(@service_account_path))["client_email"]
115
+ end
116
+
117
+ def issuer_id
118
+ @company[:issuer_id]
119
+ end
120
+
121
+ def key_id
122
+ JSON.parse(File.read(@service_account_path))["private_key_id"]
123
+ end
124
+
125
+ def private_key
126
+ OpenSSL::PKey::RSA.new(
127
+ JSON.parse(File.read(@service_account_path))["private_key"]
128
+ )
129
+ end
130
+
131
+ def build_loyalty_object
132
+ object = {
133
+ id: "#{issuer_id}.#{@customer[:id]}",
134
+ classId: "#{issuer_id}.#{@company[:class_id]}",
135
+ state: "active",
136
+ accountId: @customer[:id],
137
+ accountName: "#{@customer[:first_name]} #{@customer[:last_name]}",
138
+ loyaltyPoints: {
139
+ label: "Punti",
140
+ balance: {
141
+ int: @customer[:points] || 0
142
+ }
143
+ }
144
+ }
145
+
146
+ if @company[:background_color]
147
+ object[:hexBackgroundColor] = @company[:background_color]
148
+ end
149
+
150
+ if @company[:font_color]
151
+ object[:hexFontColor] = @company[:font_color]
152
+ end
153
+
154
+ image_modules = []
155
+ if @company[:logo_uri]
156
+ image_modules << {
157
+ mainImage: {
158
+ sourceUri: { uri: @company[:logo_uri] }
159
+ }
160
+ }
161
+ end
162
+ if @company[:hero_image_uri]
163
+ image_modules << {
164
+ mainImage: {
165
+ sourceUri: { uri: @company[:hero_image_uri] }
166
+ }
167
+ }
168
+ end
169
+ object[:imageModulesData] = image_modules unless image_modules.empty?
170
+
171
+ # Note: merchantLocations requires special authorization from Google
172
+ # For now, we'll skip locations to avoid API errors
173
+ # if @company[:locations].is_a?(Array)
174
+ # object[:merchantLocations] = @company[:locations].map do |loc|
175
+ # {
176
+ # latitude: loc[:latitude] || loc["latitude"],
177
+ # longitude: loc[:longitude] || loc["longitude"]
178
+ # }
179
+ # end
180
+ # end
181
+
182
+ if @customer[:qr_value]
183
+ object[:barcode] = {
184
+ type: "QR_CODE",
185
+ value: @customer[:qr_value]
186
+ }
187
+ elsif @customer[:barcode_value]
188
+ object[:barcode] = {
189
+ type: (@customer[:barcode_type] || "QR_CODE"),
190
+ value: @customer[:barcode_value]
191
+ }
192
+ end
193
+
194
+ object
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+
7
+ module WalletPasskit
8
+ module Google
9
+ class PassUpdater
10
+ def initialize(object_id:, service_account_path:)
11
+ @object_id = object_id
12
+ @service_account_path = service_account_path
13
+ end
14
+
15
+ def update_points(new_point_value:)
16
+ patch({ loyaltyPoints: { balance: { int: new_point_value }}})
17
+ end
18
+
19
+ # Recupera un oggetto loyalty specifico dal Google Wallet
20
+ def retrieve_object
21
+ uri = URI("https://walletobjects.googleapis.com/walletobjects/v1/loyaltyObject/#{@object_id}")
22
+ req = Net::HTTP::Get.new(uri)
23
+ req["Authorization"] = "Bearer #{access_token}"
24
+
25
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
26
+ http.request(req)
27
+ end
28
+
29
+ raise WalletPasskit::Error, "Errore API: #{res.body}" unless res.is_a?(Net::HTTPSuccess)
30
+ JSON.parse(res.body)
31
+ end
32
+
33
+ # Metodo pubblico per recuperare l'oggetto
34
+ def get_object
35
+ retrieve_object
36
+ end
37
+
38
+ # Metodi per ottenere informazioni specifiche dell'oggetto
39
+ def get_points
40
+ object = retrieve_object
41
+ object.dig('loyaltyPoints', 'balance', 'int') || 0
42
+ end
43
+
44
+ def get_state
45
+ object = retrieve_object
46
+ object['state']
47
+ end
48
+
49
+ def get_account_name
50
+ object = retrieve_object
51
+ object['accountName']
52
+ end
53
+
54
+ def get_account_id
55
+ object = retrieve_object
56
+ object['accountId']
57
+ end
58
+
59
+ def get_creation_time
60
+ object = retrieve_object
61
+ object['createTime']
62
+ end
63
+
64
+ def get_update_time
65
+ object = retrieve_object
66
+ object['updateTime']
67
+ end
68
+
69
+ # Metodo di classe per recuperare un oggetto senza creare un'istanza
70
+ def self.retrieve_object(object_id:, service_account_path:)
71
+ new(object_id: object_id, service_account_path: service_account_path).get_object
72
+ end
73
+
74
+ private
75
+
76
+ def patch(data)
77
+ uri = URI("https://walletobjects.googleapis.com/walletobjects/v1/loyaltyObject/#{@object_id}")
78
+ req = Net::HTTP::Patch.new(uri, "Content-Type" => "application/json")
79
+ req["Authorization"] = "Bearer #{access_token}"
80
+ req.body = data.to_json
81
+
82
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
83
+ http.request(req)
84
+ end
85
+
86
+ raise WalletPasskit::Error, "Errore API: #{res.body}" unless res.is_a?(Net::HTTPSuccess)
87
+ JSON.parse(res.body)
88
+ end
89
+
90
+ def access_token
91
+ WalletPasskit::Google::Auth.access_token(service_account_path: @service_account_path)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "google/pass"
4
+ require_relative "google/pass_updater"
5
+ require_relative "google/auth"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'rails'
5
+ rescue LoadError
6
+ end
7
+
8
+ module WalletPasskit
9
+ if defined?(Rails)
10
+ class Railtie < ::Rails::Railtie
11
+ # Allow configuration via Rails.application.config.wallet_passkit
12
+ config.wallet_passkit = ActiveSupport::OrderedOptions.new
13
+
14
+ initializer 'wallet_passkit.configure' do |app|
15
+ cfg = app.config.wallet_passkit
16
+ WalletPasskit.configure do |c|
17
+ c.apple_pass_certificate_p12_path = cfg.apple_pass_certificate_p12_path if cfg.key?(:apple_pass_certificate_p12_path)
18
+ c.apple_pass_certificate_password = cfg.apple_pass_certificate_password if cfg.key?(:apple_pass_certificate_password)
19
+ c.apple_wwdr_certificate_path = cfg.apple_wwdr_certificate_path if cfg.key?(:apple_wwdr_certificate_path)
20
+ c.apple_team_identifier = cfg.apple_team_identifier if cfg.key?(:apple_team_identifier)
21
+ c.apple_organization_name = cfg.apple_organization_name if cfg.key?(:apple_organization_name)
22
+
23
+ c.google_service_account_credentials = cfg.google_service_account_credentials if cfg.key?(:google_service_account_credentials)
24
+ c.google_issuer_id = cfg.google_issuer_id if cfg.key?(:google_issuer_id)
25
+ c.google_class_prefix = cfg.google_class_prefix if cfg.key?(:google_class_prefix)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WalletPasskit
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "json"
5
+ require "digest/sha1"
6
+ require "zip"
7
+
8
+ require_relative "wallet_passkit/version"
9
+ require_relative "wallet_passkit/configuration"
10
+ require_relative "wallet_passkit/google"
11
+ require_relative "wallet_passkit/apple"
12
+ require_relative "wallet_passkit/railtie"
13
+ require_relative "wallet_passkit/error"
14
+
15
+ module WalletPasskit
16
+
17
+ class Error < StandardError; end
18
+
19
+ def self.configure
20
+ yield(config)
21
+ end
22
+
23
+ def self.config
24
+ @config ||= Configuration.new
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wallet_passkit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gioggi
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rubyzip
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.3'
26
+ - !ruby/object:Gem::Dependency
27
+ name: jwt
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.7'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.7'
40
+ - !ruby/object:Gem::Dependency
41
+ name: googleauth
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 0.16.0
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 0.16.0
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '13.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '13.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rspec
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '3.12'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '3.12'
82
+ - !ruby/object:Gem::Dependency
83
+ name: webmock
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '3.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '3.0'
96
+ description: A modern, lightweight Ruby gem for generating Apple Wallet passes (.pkpass)
97
+ with OpenSSL signing, easy Rails integration services, and a minimal Google Wallet
98
+ Save link generator.
99
+ email:
100
+ - info@giovanniesposito.it
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - README.md
106
+ - lib/wallet_passkit.rb
107
+ - lib/wallet_passkit/apple.rb
108
+ - lib/wallet_passkit/apple/service.rb
109
+ - lib/wallet_passkit/apple/signer.rb
110
+ - lib/wallet_passkit/configuration.rb
111
+ - lib/wallet_passkit/error.rb
112
+ - lib/wallet_passkit/google.rb
113
+ - lib/wallet_passkit/google/README.md
114
+ - lib/wallet_passkit/google/auth.rb
115
+ - lib/wallet_passkit/google/pass.rb
116
+ - lib/wallet_passkit/google/pass_updater.rb
117
+ - lib/wallet_passkit/railtie.rb
118
+ - lib/wallet_passkit/version.rb
119
+ homepage: https://example.com/wallet_passkit
120
+ licenses:
121
+ - MIT
122
+ metadata: {}
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '2.7'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubygems_version: 3.6.9
138
+ specification_version: 4
139
+ summary: Generate Apple Wallet .pkpass and integrate with Rails; scaffold for Google
140
+ Wallet
141
+ test_files: []