spree_google_products 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 +7 -0
- data/README.md +177 -0
- data/Rakefile +21 -0
- data/app/assets/config/spree_google_products_manifest.js +5 -0
- data/app/assets/images/app_icons/google_logo.svg +1 -0
- data/app/assets/images/app_icons/google_merchant_logo.svg +52 -0
- data/app/assets/images/app_icons/welcome_scene.svg +1 -0
- data/app/controllers/spree/admin/google_merchant_settings_controller.rb +182 -0
- data/app/controllers/spree/admin/google_shopping/dashboard_controller.rb +64 -0
- data/app/controllers/spree/admin/google_shopping/issues_controller.rb +42 -0
- data/app/controllers/spree/admin/google_shopping/products_controller.rb +79 -0
- data/app/controllers/spree/admin/google_shopping/taxons_controller.rb +39 -0
- data/app/helpers/spree/admin/google_shopping_helper.rb +39 -0
- data/app/javascript/spree_google_products/application.js +16 -0
- data/app/javascript/spree_google_products/controllers/spree_google_products_controller.js +7 -0
- data/app/jobs/spree/google_shopping/fetch_status_job.rb +23 -0
- data/app/jobs/spree/google_shopping/sync_all_job.rb +18 -0
- data/app/jobs/spree/google_shopping/sync_product_job.rb +35 -0
- data/app/jobs/spree_google_products/base_job.rb +5 -0
- data/app/models/spree/google_credential.rb +29 -0
- data/app/models/spree/google_product_attribute.rb +9 -0
- data/app/models/spree/google_taxon.rb +5 -0
- data/app/models/spree/google_variant_attribute.rb +6 -0
- data/app/models/spree/product_decorator.rb +29 -0
- data/app/models/spree/store_decorator.rb +9 -0
- data/app/models/spree/variant_decorator.rb +8 -0
- data/app/models/spree_google_products/ability.rb +10 -0
- data/app/services/spree/google_shopping/content_service.rb +215 -0
- data/app/services/spree/google_shopping/status_service.rb +150 -0
- data/app/services/spree/google_token_service.rb +59 -0
- data/app/views/spree/admin/google_merchant_settings/edit.html.erb +331 -0
- data/app/views/spree/admin/google_shopping/dashboard/index.html.erb +121 -0
- data/app/views/spree/admin/google_shopping/issues/index.html.erb +106 -0
- data/app/views/spree/admin/google_shopping/products/_filters.html.erb +19 -0
- data/app/views/spree/admin/google_shopping/products/edit.html.erb +336 -0
- data/app/views/spree/admin/google_shopping/products/index.html.erb +131 -0
- data/app/views/spree/admin/google_shopping/products/issues.html.erb +48 -0
- data/app/views/spree/admin/products/google_shopping.html.erb +63 -0
- data/app/views/spree_google_products/_head.html.erb +1 -0
- data/config/importmap.rb +6 -0
- data/config/initializers/assets.rb +8 -0
- data/config/initializers/force_encryption_keys.rb +24 -0
- data/config/initializers/spree.rb +32 -0
- data/config/initializers/spree_google_products.rb +29 -0
- data/config/locales/en.yml +35 -0
- data/config/routes.rb +33 -0
- data/db/migrate/20260112000000_add_spree_google_shopping_tables.rb +89 -0
- data/lib/generators/spree_google_products/install/install_generator.rb +84 -0
- data/lib/generators/spree_google_products/uninstall/uninstall_generator.rb +55 -0
- data/lib/spree_google_products/configuration.rb +13 -0
- data/lib/spree_google_products/engine.rb +37 -0
- data/lib/spree_google_products/factories.rb +6 -0
- data/lib/spree_google_products/version.rb +7 -0
- data/lib/spree_google_products.rb +13 -0
- data/lib/tasks/spree_google_products.rake +41 -0
- metadata +190 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f5d3d6b3ccd94d66bee77b1760f4b1a6943b942a329c85dbc8b9f0750540e53a
|
|
4
|
+
data.tar.gz: eb9538793bd6233441a77c8892de2a43c5da94960697e7fd9ad6fe426953976e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a7656f0f6f6ce4f5645a8a315cf2fcc035be97a912c576c4b973a7d504723853665c4aae3d62c1968461654af36472ba911a23b6afe037b29572edcd16d42a12
|
|
7
|
+
data.tar.gz: 11ad2e4b1590638d8c14fcdec637fa2b6bafdd8fb3907d58c45c653162fa8dd38bfd9229075e521da91120ba3dc28aa5b5b399468bedac35181a5c27fc733dde
|
data/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Spree Google Shopping (Merchant Center)<br>
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
<br>
|
|
6
|
+
A production-grade Spree Commerce extension that integrates directly with Google Merchant Center. Automatically sync products, variants, inventory, and pricing in real-time using the Content API v2.1
|
|
7
|
+
|
|
8
|
+
<br>
|
|
9
|
+
Includes a full Admin Dashboard for tracking approval status, fixing data quality issues, and managing OAuth connections securely.
|
|
10
|
+
|
|
11
|
+
<br>
|
|
12
|
+
|
|
13
|
+
## 🚀 Key Features
|
|
14
|
+
1. Real-Time Sync: Products are pushed to Google immediately upon update/create via background jobs.<br>
|
|
15
|
+
2. OAuth 2.0 Integration: Secure, token-based authentication (No static JSON key files required).<br>
|
|
16
|
+
3. Admin Dashboard: Live visualization of Approved, Pending, and Disapproved products.<br>
|
|
17
|
+
4. Data Quality Issues: View specific error messages from Google (e.g., "Missing GTIN", "Image too small") directly in Spree.<br>
|
|
18
|
+
5. Granular Control:<br>
|
|
19
|
+
• Set Global Defaults (Target Country, Currency, Shipping Rules).<br>
|
|
20
|
+
• Override attributes per Product (Brand, MPN, GTIN, Gender, Age Group).
|
|
21
|
+
6. Drill-Down Taxonomy: Built-in selector for Google's official Product Taxonomy (6,000+ categories).
|
|
22
|
+
7. Secure: Uses Rails 7 Active Record Encryption to store OAuth tokens safely.
|
|
23
|
+
|
|
24
|
+
<br>
|
|
25
|
+
|
|
26
|
+
## 📦 Installation
|
|
27
|
+
1. Add this line to your application's Gemfile:
|
|
28
|
+
```ruby
|
|
29
|
+
gem 'spree_google_products', github: 'your-username/spree_google_products'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
2. Install the gem:
|
|
33
|
+
```ruby
|
|
34
|
+
bundle install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
3. Run the Installer:
|
|
38
|
+
This command will copy migrations, generate encryption keys, and seed the Google Taxonomy database.
|
|
39
|
+
```bash
|
|
40
|
+
bundle exec rails g spree_google_products:install
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Follow the on-screen prompts to run migrations and seed data.
|
|
44
|
+
|
|
45
|
+
<br>
|
|
46
|
+
|
|
47
|
+
## ⚙️ Configuration
|
|
48
|
+
### Google Cloud Console Setup<br>
|
|
49
|
+
Before using the extension, you must create credentials in Google Cloud.<br>
|
|
50
|
+
1. Go to [Google Cloud Console.](https://console.cloud.google.com/)<br>
|
|
51
|
+
2. Create a new project (or select existing).<br>
|
|
52
|
+
3. Enable API: Go to "APIs & Services" > "Library" -> Search for "Content API for Shopping" -> Enable it.<br>
|
|
53
|
+
4. Create Credentials:<br>
|
|
54
|
+
|
|
55
|
+
• Go to "APIs & Services" > "Credentials".<br>
|
|
56
|
+
|
|
57
|
+
• Click Create Credentials -> OAuth Client ID.<br>
|
|
58
|
+
|
|
59
|
+
• Application Type: Web Application.<br>
|
|
60
|
+
|
|
61
|
+
• Authorized Redirect URI: https://your-store.com/admin/google_merchant_settings/callback<br>
|
|
62
|
+
(Note: Use http://localhost:3000/... for local development).<br>
|
|
63
|
+
5. Copy the Client ID and Client Secret.<br>
|
|
64
|
+
<br>
|
|
65
|
+
<img width="900" height="auto" alt="Screenshot 2026-01-11 at 11 58 54 PM" src="https://github.com/user-attachments/assets/bff8a7cc-050d-4fa1-9bfe-190719ae984a" />
|
|
66
|
+
<br>
|
|
67
|
+
|
|
68
|
+
### Environment Variables<br>
|
|
69
|
+
The installer automatically adds these to your .env file. Fill in your Google credentials:<br>
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
GOOGLE_CLIENT_ID=your_client_id_here
|
|
73
|
+
GOOGLE_CLIENT_SECRET=your_client_secret_here
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
<br>
|
|
77
|
+
|
|
78
|
+
### Connect in Spree Admin
|
|
79
|
+
1. Login to your Spree Admin Panel.
|
|
80
|
+
2. Navigate to Google Shopping > Settings (Sidebar).
|
|
81
|
+
3. Click Connect Account and log in with the Google Account that manages your Merchant Center.
|
|
82
|
+
4. Once connected, enter your Merchant Center ID and Target Country/Currency.
|
|
83
|
+
5. Click Save Settings.
|
|
84
|
+
<br>
|
|
85
|
+
<img width="900" height="auto" alt="Screenshot 2026-01-11 at 11 59 17 PM" src="https://github.com/user-attachments/assets/fea6f8c2-dd1e-443c-8906-dd61869b8226" />
|
|
86
|
+
<br>
|
|
87
|
+
|
|
88
|
+
## 🛠️ Usage
|
|
89
|
+
|
|
90
|
+
### Dashboard
|
|
91
|
+
Visit Google Shopping > Dashboard to see a live overview of your product feed health.
|
|
92
|
+
• Approved: Live on Google Shopping.
|
|
93
|
+
|
|
94
|
+
• Limited: Live but restricted (e.g., adult content, partial regions).
|
|
95
|
+
|
|
96
|
+
• Disapproved: Critical issues preventing display.
|
|
97
|
+
|
|
98
|
+
• Sync Now: Force a full catalog sync manually.
|
|
99
|
+
|
|
100
|
+
<img width="900" height="auto" alt="Screenshot 2026-01-12 at 12 43 34 AM" src="https://github.com/user-attachments/assets/b53174b2-37ee-42ea-9526-00630b46c60e" />
|
|
101
|
+
|
|
102
|
+
<br>
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
### Managing Products
|
|
106
|
+
You can manage Google-specific attributes in the Product Edit tab under "Google Shopping Attributes".
|
|
107
|
+
• GTIN / MPN: Crucial for approval. If your products have barcodes, enter them here.
|
|
108
|
+
|
|
109
|
+
• Google Category: Use the drill-down selector to pick the exact Google Taxonomy ID.
|
|
110
|
+
|
|
111
|
+
• Demographics: Set Gender, Age Group, and Condition (New/Used).
|
|
112
|
+
|
|
113
|
+
<br>
|
|
114
|
+
<img width="900" height="auto" alt="Screenshot 2026-01-12 at 12 51 02 AM" src="https://github.com/user-attachments/assets/37c064cf-d794-42bb-90eb-3ade6ec2bbab" />
|
|
115
|
+
<img width="900" height="auto" alt="Screenshot 2026-01-12 at 12 32 11 AM" src="https://github.com/user-attachments/assets/aee1955d-481f-47d3-a15f-c7a4670f9e78" />
|
|
116
|
+
<img width="871" height="456" alt="Screenshot 2026-01-12 at 12 39 32 AM" src="https://github.com/user-attachments/assets/2a509733-2151-48c0-8b63-2d3db654b797" />
|
|
117
|
+
|
|
118
|
+
<br>
|
|
119
|
+
|
|
120
|
+
### Background Jobs
|
|
121
|
+
This extension uses ActiveJob. Ensure you have a queue adapter configured (Sidekiq, Solid Queue, or Delayed Job) for production.<br>
|
|
122
|
+
• Spree::GoogleShopping::SyncProductJob: Syncs individual product updates.<br>
|
|
123
|
+
• Spree::GoogleShopping::SyncAllJob: Bulk syncs entire catalog.<br>
|
|
124
|
+
• Spree::GoogleShopping::FetchStatusJob: Pulls approval status from Google.<br>
|
|
125
|
+
|
|
126
|
+
<br>
|
|
127
|
+
|
|
128
|
+
## 🔒 Security & Encryption
|
|
129
|
+
This plugin strictly adheres to security best practices. OAuth Refresh Tokens are encrypted at rest in the database using Rails Active Record Encryption.<br>
|
|
130
|
+
|
|
131
|
+
The installer generates these keys in your .env file automatically:
|
|
132
|
+
|
|
133
|
+
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY<br>
|
|
134
|
+
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY<br>
|
|
135
|
+
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT<br>
|
|
136
|
+
|
|
137
|
+
<br>
|
|
138
|
+
|
|
139
|
+
### ⚠️ IMPORTANT: Keep these keys safe. If you lose them, you will need to disconnect and reconnect your Google Account.
|
|
140
|
+
|
|
141
|
+
<br>
|
|
142
|
+
|
|
143
|
+
## 🗑️ Uninstallation
|
|
144
|
+
To cleanly remove the extension, drop tables, and cleanup migrations, run:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
bundle exec rails g spree_google_products:uninstall
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
This command will ask for confirmation before dropping the Google-related tables.
|
|
151
|
+
|
|
152
|
+
<br>
|
|
153
|
+
|
|
154
|
+
## 🗺️ Roadmap
|
|
155
|
+
• [x] Google Merchant Center (Content API) Sync<br>
|
|
156
|
+
• [x] Product Approval Status Dashboard<br>
|
|
157
|
+
• [x] Issue Reporting<br>
|
|
158
|
+
• [ ] Google Ads Integration: Create Performance Max campaigns directly from Spree.<br>
|
|
159
|
+
• [ ] Conversion Tracking: Auto-inject Google Ads conversion pixels.<br>
|
|
160
|
+
• [ ] Free Listings: specialized support for "Free Listings" enhanced attributes.<br>
|
|
161
|
+
|
|
162
|
+
<br>
|
|
163
|
+
|
|
164
|
+
## ❓ FAQ
|
|
165
|
+
Q: I get "Missing Active Record encryption credential" error.
|
|
166
|
+
A: Restart your server. The encryption keys are loaded from .env on boot. If using a custom deployment, ensure the ACTIVE_RECORD_ENCRYPTION_* variables are set in your environment.
|
|
167
|
+
|
|
168
|
+
Q: Why are my products still "Pending" after syncing?
|
|
169
|
+
A: Google takes 3-5 business days to review new products. Check the Dashboard for real-time status updates.
|
|
170
|
+
|
|
171
|
+
Q: Can I sync to multiple countries?
|
|
172
|
+
A: Currently, the extension supports one primary Target Country per store. Multi-country feeds via the "Shipping" attribute are planned for v2.0.
|
|
173
|
+
|
|
174
|
+
<br>
|
|
175
|
+
|
|
176
|
+
### License
|
|
177
|
+
Copyright (c) 2026. Released under the BSD-3-Clause [License](https://github.com/umeshravani/spree_google_products/blob/main/LICENSE.md).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'bundler'
|
|
2
|
+
Bundler::GemHelper.install_tasks
|
|
3
|
+
|
|
4
|
+
require 'rspec/core/rake_task'
|
|
5
|
+
require 'spree/testing_support/extension_rake'
|
|
6
|
+
|
|
7
|
+
RSpec::Core::RakeTask.new
|
|
8
|
+
|
|
9
|
+
task :default do
|
|
10
|
+
if Dir['spec/dummy'].empty?
|
|
11
|
+
Rake::Task[:test_app].invoke
|
|
12
|
+
Dir.chdir('../../')
|
|
13
|
+
end
|
|
14
|
+
Rake::Task[:spec].invoke
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
desc 'Generates a dummy app for testing'
|
|
18
|
+
task :test_app do
|
|
19
|
+
ENV['LIB_NAME'] = 'spree_google_products'
|
|
20
|
+
Rake::Task['extension:test_app'].invoke
|
|
21
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/><path d="M1 1h22v22H1z" fill="none"/></svg>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
2
|
+
<mask id="mask0_2_319" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="3" y="3" width="30" height="30">
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M30 32.25H6C4.7625 32.25 3.75 31.2375 3.75 30V6C3.75 4.7625 4.7625 3.75 6 3.75H30C31.2375 3.75 32.25 4.7625 32.25 6V30C32.25 31.2375 31.2375 32.25 30 32.25Z" fill="white"/>
|
|
4
|
+
</mask>
|
|
5
|
+
<g mask="url(#mask0_2_319)">
|
|
6
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.75 32.25V3.75H32.25V32.25H3.75ZM22.1869 9V8.99625C22.7794 8.96438 23.25 8.48063 23.25 7.8825C23.25 7.28437 22.7794 6.79875 22.1869 6.76688V6.76312H19.4981C19.4981 6.75938 19.4986 6.75563 19.4991 6.75188C19.4995 6.74813 19.5 6.74437 19.5 6.74062C19.5 5.9175 18.8287 5.25 18 5.25C17.1713 5.25 16.5 5.9175 16.5 6.74062C16.5 6.74437 16.5005 6.74813 16.5009 6.75188C16.5014 6.75563 16.5019 6.75938 16.5019 6.76312H13.8131V6.76688C13.2206 6.80063 12.75 7.28625 12.75 7.8825C12.75 8.47875 13.2206 8.96438 13.8131 8.99625V9H22.1869Z" fill="#4285F4"/>
|
|
7
|
+
</g>
|
|
8
|
+
<mask id="mask1_2_319" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="3" y="3" width="30" height="30">
|
|
9
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M30 32.25H6C4.7625 32.25 3.75 31.2375 3.75 30V6C3.75 4.7625 4.7625 3.75 6 3.75H30C31.2375 3.75 32.25 4.7625 32.25 6V30C32.25 31.2375 31.2375 32.25 30 32.25Z" fill="white"/>
|
|
10
|
+
</mask>
|
|
11
|
+
<g mask="url(#mask1_2_319)">
|
|
12
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M30 3.75H6C4.7625 3.75 3.75 4.7625 3.75 6V6.1875C3.75 4.95 4.7625 3.9375 6 3.9375H30C31.2375 3.9375 32.25 4.95 32.25 6.1875V6C32.25 4.7625 31.2375 3.75 30 3.75Z" fill="white" fill-opacity="0.2"/>
|
|
13
|
+
</g>
|
|
14
|
+
<mask id="mask2_2_319" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="3" y="3" width="30" height="30">
|
|
15
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M30 32.25H6C4.7625 32.25 3.75 31.2375 3.75 30V6C3.75 4.7625 4.7625 3.75 6 3.75H30C31.2375 3.75 32.25 4.7625 32.25 6V30C32.25 31.2375 31.2375 32.25 30 32.25Z" fill="white"/>
|
|
16
|
+
</mask>
|
|
17
|
+
<g mask="url(#mask2_2_319)">
|
|
18
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.75 10.5V3.75H32.25V10.5H3.75ZM22.1869 9V8.99625C22.7794 8.96438 23.25 8.48063 23.25 7.8825C23.25 7.28437 22.7794 6.79875 22.1869 6.76688V6.76312H19.4981C19.4981 6.75938 19.4986 6.75563 19.4991 6.75188C19.4995 6.74813 19.5 6.74437 19.5 6.74062C19.5 5.9175 18.8287 5.25 18 5.25C17.1713 5.25 16.5 5.9175 16.5 6.74062C16.5 6.74437 16.5005 6.74813 16.5009 6.75188C16.5014 6.75563 16.5019 6.75938 16.5019 6.76312H13.8131V6.76688C13.2206 6.80063 12.75 7.28625 12.75 7.8825C12.75 8.47875 13.2206 8.96438 13.8131 8.99625V9H22.1869Z" fill="#3F51B5"/>
|
|
19
|
+
</g>
|
|
20
|
+
<mask id="mask3_2_319" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="3" y="3" width="30" height="30">
|
|
21
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M30 32.25H6C4.7625 32.25 3.75 31.2375 3.75 30V6C3.75 4.7625 4.7625 3.75 6 3.75H30C31.2375 3.75 32.25 4.7625 32.25 6V30C32.25 31.2375 31.2375 32.25 30 32.25Z" fill="white"/>
|
|
22
|
+
</mask>
|
|
23
|
+
<g mask="url(#mask3_2_319)">
|
|
24
|
+
<path opacity="0.2" fill-rule="evenodd" clip-rule="evenodd" d="M22.1868 9.00937V9.01312H13.8131V9.00937C13.2525 8.97937 12.8006 8.53875 12.7556 7.98187C12.7537 8.01375 12.7518 8.04375 12.7518 8.07562C12.7518 8.67562 13.2225 9.165 13.815 9.19687V9.20062H22.1906V9.19687C22.7831 9.165 23.2537 8.67562 23.2537 8.07562C23.2537 8.04375 23.2518 8.01375 23.25 7.98187C23.1993 8.53687 22.7475 8.9775 22.1868 9.00937Z" fill="white"/>
|
|
25
|
+
</g>
|
|
26
|
+
<mask id="mask4_2_319" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="3" y="3" width="30" height="30">
|
|
27
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M30 32.25H6C4.7625 32.25 3.75 31.2375 3.75 30V6C3.75 4.7625 4.7625 3.75 6 3.75H30C31.2375 3.75 32.25 4.7625 32.25 6V30C32.25 31.2375 31.2375 32.25 30 32.25Z" fill="white"/>
|
|
28
|
+
</mask>
|
|
29
|
+
<g mask="url(#mask4_2_319)">
|
|
30
|
+
<path opacity="0.2" fill-rule="evenodd" clip-rule="evenodd" d="M22.1869 6.5775V6.57375H19.4981C19.4981 6.56625 19.5 6.55875 19.5 6.55125C19.5 5.7225 18.8288 5.05125 18 5.05125C17.1713 5.05125 16.5 5.7225 16.5 6.55125C16.5 6.55875 16.5019 6.56625 16.5019 6.57375H13.8131V6.5775C13.2206 6.60937 12.75 7.09875 12.75 7.69875C12.75 7.73062 12.7519 7.76062 12.7538 7.7925C12.8006 7.23562 13.2506 6.795 13.8113 6.765V6.76125H16.5C16.5 6.75375 16.4981 6.74625 16.4981 6.73875C16.4981 5.91 17.1694 5.23875 17.9981 5.23875C18.8269 5.23875 19.4981 5.91 19.4981 6.73875C19.4981 6.74625 19.4963 6.75375 19.4963 6.76125H22.185V6.765C22.7456 6.795 23.1975 7.23562 23.2425 7.7925C23.2444 7.76062 23.2463 7.73062 23.2463 7.69875C23.25 7.09875 22.7794 6.60937 22.1869 6.5775Z" fill="#1A237E"/>
|
|
31
|
+
</g>
|
|
32
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M30 32.0625H6C4.7625 32.0625 3.75 31.05 3.75 29.8125V30C3.75 31.2375 4.7625 32.25 6 32.25H30C31.2375 32.25 32.25 31.2375 32.25 30V29.8125C32.25 31.05 31.2375 32.0625 30 32.0625Z" fill="#1A237E" fill-opacity="0.2"/>
|
|
33
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 3.9375H30C31.2375 3.9375 32.25 4.95 32.25 6.1875V6C32.25 4.7625 31.2375 3.75 30 3.75H6C4.7625 3.75 3.75 4.7625 3.75 6V6.1875C3.75 4.95 4.7625 3.9375 6 3.9375Z" fill="white" fill-opacity="0.2"/>
|
|
34
|
+
<rect opacity="0.2" x="9" y="12.375" width="18.765" height="18.765" fill="url(#pattern0)"/>
|
|
35
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.67 14.58L24.795 21.705C24.9994 21.9075 25.125 22.1887 25.125 22.5C25.125 22.8094 24.9994 23.0906 24.795 23.295L19.92 28.17C19.7175 28.3744 19.4344 28.5 19.125 28.5C18.8156 28.5 18.5344 28.3744 18.33 28.17L11.205 21.045C11.0006 20.8425 10.875 20.5613 10.875 20.25V15.375C10.875 14.7544 11.3794 14.25 12 14.25H16.875C17.1863 14.25 17.4675 14.3756 17.67 14.58ZM12 16.125C12 16.5394 12.3356 16.875 12.75 16.875C13.1644 16.875 13.5 16.5394 13.5 16.125C13.5 15.7106 13.1644 15.375 12.75 15.375C12.3356 15.375 12 15.7106 12 16.125Z" fill="#F5F5F5"/>
|
|
36
|
+
<path opacity="0.2" fill-rule="evenodd" clip-rule="evenodd" d="M17.67 14.58L24.795 21.705C24.9994 21.9075 25.125 22.1887 25.125 22.5C25.125 22.521 25.1234 22.5411 25.1217 22.5616C25.1209 22.5722 25.12 22.5829 25.1194 22.5938C25.0969 22.3219 24.9787 22.0762 24.795 21.8925L17.67 14.7675C17.4675 14.5631 17.1863 14.4375 16.875 14.4375H12C11.3794 14.4375 10.875 14.9419 10.875 15.5625V15.375C10.875 14.7544 11.3794 14.25 12 14.25H16.875C17.1863 14.25 17.4675 14.3756 17.67 14.58ZM13.5 16.125C13.5 16.5394 13.1644 16.875 12.75 16.875C12.3356 16.875 12 16.5394 12 16.125C12 16.1016 12.003 16.0792 12.0061 16.0563C12.0072 16.0481 12.0084 16.0397 12.0094 16.0312C12.0562 16.4006 12.3675 16.6875 12.75 16.6875C13.1325 16.6875 13.4437 16.4006 13.4906 16.0312C13.4916 16.0397 13.4928 16.0481 13.4939 16.0563C13.497 16.0792 13.5 16.1016 13.5 16.125Z" fill="white"/>
|
|
37
|
+
<path opacity="0.2" fill-rule="evenodd" clip-rule="evenodd" d="M13.4906 16.2188C13.4437 15.8494 13.1325 15.5625 12.75 15.5625C12.3675 15.5625 12.0562 15.8494 12.0094 16.2188C12.0084 16.2103 12.0072 16.2019 12.0061 16.1937C12.003 16.1708 12 16.1484 12 16.125C12 15.7106 12.3356 15.375 12.75 15.375C13.1644 15.375 13.5 15.7106 13.5 16.125C13.5 16.1484 13.497 16.1708 13.4939 16.1937C13.4928 16.2019 13.4916 16.2103 13.4906 16.2188ZM19.92 28.17L24.795 23.295C24.9787 23.1131 25.0969 22.8675 25.1194 22.5938C25.1231 22.6256 25.125 22.6556 25.125 22.6875C25.125 22.9969 24.9994 23.2781 24.795 23.4825L19.92 28.3575C19.7175 28.5619 19.4344 28.6875 19.125 28.6875C18.8156 28.6875 18.5344 28.5619 18.33 28.3575L11.205 21.2325C11.0006 21.03 10.875 20.7488 10.875 20.4375V20.25C10.875 20.5613 11.0006 20.8425 11.205 21.045L18.33 28.17C18.5325 28.3744 18.8156 28.5 19.125 28.5C19.4344 28.5 19.7156 28.3744 19.92 28.17Z" fill="#1A237E"/>
|
|
38
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.25 29.1713L24.885 21.8063C25.035 21.9975 25.125 22.2375 25.125 22.5C25.125 22.8113 24.9994 23.0925 24.795 23.295L19.92 28.17C19.7156 28.3744 19.4344 28.5 19.125 28.5C18.8625 28.5 18.6225 28.41 18.4313 28.26L22.4213 32.25H32.25V29.1713Z" fill="url(#paint0_linear_2_319)"/>
|
|
39
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.25 6V30C32.25 31.2375 31.2375 32.25 30 32.2481H6C4.7625 32.2481 3.75 31.2375 3.75 30V5.99813C3.75 4.7625 4.7625 3.75 6 3.75H30C31.2375 3.75 32.25 4.7625 32.25 6ZM22.1869 9V8.99625C22.7794 8.96438 23.25 8.48063 23.25 7.8825C23.25 7.28437 22.7794 6.79875 22.1869 6.76688V6.76312H19.4981C19.4981 6.75938 19.4986 6.75563 19.4991 6.75188C19.4995 6.74813 19.5 6.74437 19.5 6.74062C19.5 5.9175 18.8287 5.25 18 5.25C17.1713 5.25 16.5 5.9175 16.5 6.74062C16.5 6.74437 16.5005 6.74813 16.5009 6.75188C16.5014 6.75563 16.5019 6.75938 16.5019 6.76312H13.8131V6.76688C13.2206 6.80063 12.75 7.28625 12.75 7.8825C12.75 8.47875 13.2206 8.96438 13.8131 8.99625V9H22.1869Z" fill="url(#paint1_radial_2_319)"/>
|
|
40
|
+
<defs>
|
|
41
|
+
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
|
42
|
+
</pattern>
|
|
43
|
+
<linearGradient id="paint0_linear_2_319" x1="17.0167" y1="28.6632" x2="22.4013" y2="38.0903" gradientUnits="userSpaceOnUse">
|
|
44
|
+
<stop stop-color="#1A237E" stop-opacity="0.2"/>
|
|
45
|
+
<stop offset="1" stop-color="#1A237E" stop-opacity="0.02"/>
|
|
46
|
+
</linearGradient>
|
|
47
|
+
<radialGradient id="paint1_radial_2_319" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(4.6875 4.125) scale(38.1249 38.1224)">
|
|
48
|
+
<stop stop-color="white" stop-opacity="0.1"/>
|
|
49
|
+
<stop offset="1" stop-color="white" stop-opacity="0.01"/>
|
|
50
|
+
</radialGradient>
|
|
51
|
+
</defs>
|
|
52
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="316" height="112"><defs><linearGradient id="a" x1="66" y1="90" x2="102" y2="90" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#bdc1c6" stop-opacity="0"/><stop offset=".137" stop-color="#bdc1c6" stop-opacity=".021"/><stop offset=".279" stop-color="#bdc1c6" stop-opacity=".084"/><stop offset=".424" stop-color="#bdc1c6" stop-opacity=".189"/><stop offset=".57" stop-color="#bdc1c6" stop-opacity=".336"/><stop offset=".718" stop-color="#bdc1c6" stop-opacity=".525"/><stop offset=".864" stop-color="#bdc1c6" stop-opacity=".753"/><stop offset="1" stop-color="#bdc1c6"/></linearGradient><linearGradient id="b" data-name="Linear ground shadow - light-B" x1="224" y1="110" x2="246" y2="110" xlink:href="#a"/><linearGradient id="c" data-name="Linear ground shadow - light-B" x1="26" y1="110" x2="58" y2="110" xlink:href="#a"/><linearGradient id="d" x1="65.992" y1="70" x2="65.992" y2="112" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#669df6"/><stop offset=".094" stop-color="#669df6" stop-opacity=".826"/><stop offset=".225" stop-color="#669df6" stop-opacity=".61"/><stop offset=".358" stop-color="#669df6" stop-opacity=".423"/><stop offset=".49" stop-color="#669df6" stop-opacity=".27"/><stop offset=".621" stop-color="#669df6" stop-opacity=".152"/><stop offset=".75" stop-color="#669df6" stop-opacity=".068"/><stop offset=".877" stop-color="#669df6" stop-opacity=".017"/><stop offset="1" stop-color="#669df6" stop-opacity="0"/></linearGradient><linearGradient id="e" x1="260" y1="112" x2="260" y2="80" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f29900"/><stop offset="1" stop-color="#f29900" stop-opacity="0"/></linearGradient><linearGradient id="f" x1="269" y1="80" x2="269" y2="112" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fdd663"/><stop offset=".107" stop-color="#fdd663" stop-opacity=".803"/><stop offset=".236" stop-color="#fdd663" stop-opacity=".593"/><stop offset=".367" stop-color="#fdd663" stop-opacity=".411"/><stop offset=".497" stop-color="#fdd663" stop-opacity=".263"/><stop offset=".626" stop-color="#fdd663" stop-opacity=".148"/><stop offset=".754" stop-color="#fdd663" stop-opacity=".066"/><stop offset=".879" stop-color="#fdd663" stop-opacity=".017"/><stop offset="1" stop-color="#fdd663" stop-opacity="0"/></linearGradient></defs><g style="isolation:isolate"><path fill="url(#a)" style="mix-blend-mode:multiply" d="M66 88h36v4H66z"/><rect x="99" y="21" width="118" height="70" rx="3" fill="#fff"/><path d="M214 22a2.002 2.002 0 0 1 2 2v64a2.002 2.002 0 0 1-2 2H102a2.002 2.002 0 0 1-2-2V24a2.002 2.002 0 0 1 2-2h112m0-2H102a4 4 0 0 0-4 4v64a4 4 0 0 0 4 4h112a4 4 0 0 0 4-4V24a4 4 0 0 0-4-4z" fill="#e8eaed"/><path fill="url(#b)" style="mix-blend-mode:multiply" d="M224 108h22v4h-22z"/><path fill="url(#c)" style="mix-blend-mode:multiply" d="M26 108h32v4H26z"/><rect x="82" y="32" width="108" height="32" rx="4" fill="#4285f4"/><rect x="126" y="48" width="108" height="32" rx="5.367" fill="#f1f3f4"/><rect x="216" y="54" width="2" height="20" rx="1" fill="#ea4335"/><path fill="none" d="M138 56h16v16h-16z"/><path fill="#1a73e8" d="M138 62.75h16v2.5h-16z"/><path fill="#1a73e8" d="m143.083 56.447 8 13.856-2.166 1.25-8-13.856z"/><path fill="#1a73e8" d="m151.083 57.697-8 13.856-2.166-1.25 8-13.856z"/><path fill="none" d="M164 56h16v16h-16z"/><path fill="#1a73e8" d="M164 62.75h16v2.5h-16z"/><path fill="#1a73e8" d="m169.083 56.447 8 13.856-2.166 1.25-8-13.856z"/><path fill="#1a73e8" d="m177.083 57.697-8 13.856-2.166-1.25 8-13.856z"/><path fill="none" d="M190 56h16v16h-16z"/><path fill="#1a73e8" d="M190 62.75h16v2.5h-16z"/><path fill="#1a73e8" d="m195.083 56.447 8 13.856-2.166 1.25-8-13.856z"/><path fill="#1a73e8" d="m203.083 57.697-8 13.856-2.166-1.25 8-13.856z"/><rect x="98" y="38" width="2" height="20" rx="1" fill="#fff"/><path d="M180 22V10m-52.343 25.657L118 26m114.343 9.657L242 26" fill="none" stroke="#fbbc04" stroke-linecap="round" stroke-miterlimit="10" stroke-width="3"/><path d="M57.984 112a27.497 27.497 0 0 0 15.67-21.27A41.143 41.143 0 0 0 74 86.147V75l-16-5-16 5v11.147a41.143 41.143 0 0 0 .346 4.583A27.43 27.43 0 0 0 57.985 112z" fill="#4285f4"/><path d="M57.984 112a27.497 27.497 0 0 0 15.67-21.27A41.143 41.143 0 0 0 74 86.147V75l-16-5z" fill="url(#d)" style="mix-blend-mode:lighten" data-name="Shape"/><rect x="242" y="80" width="36" height="32" rx="4" fill="#fbbc04"/><path d="M250 80v-7a10 10 0 0 1 10-10 10 10 0 0 1 10 10v7" fill="none" stroke="#9aa0a6" stroke-miterlimit="10" stroke-width="3"/><rect x="242" y="80" width="36" height="32" rx="4" opacity=".5" fill="url(#e)" style="mix-blend-mode:multiply"/><path d="M274 112a4 4 0 0 0 4-4V84a4 4 0 0 0-4-4h-14v32z" fill="url(#f)" style="mix-blend-mode:lighten"/><path fill="none" d="M0 0h316v112H0z"/><path d="M260 25.971a3.637 3.637 0 0 1 4.265-3.602 10.972 10.972 0 0 1 19.47-2.519A6.4 6.4 0 0 1 292 25.971m-6.4-6.4a6.38 6.38 0 0 0-4.526 1.875m-14.831 1.939a3.646 3.646 0 0 0-2.586-1.07M20 38a9.29 9.29 0 0 1 17-5.184A5.419 5.419 0 0 1 44 38m-5.42-5.42a5.402 5.402 0 0 0-3.831 1.588M76 16a9.29 9.29 0 0 0-17-5.184A5.419 5.419 0 0 0 52 16m5.42-5.42a5.402 5.402 0 0 1 3.831 1.588" fill="none" stroke="#e8eaed" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2"/></g></svg>
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
require 'signet/oauth_2/client'
|
|
2
|
+
require 'net/http'
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
|
|
6
|
+
module Spree
|
|
7
|
+
module Admin
|
|
8
|
+
class GoogleMerchantSettingsController < ResourceController
|
|
9
|
+
helper Spree::Admin::GoogleShoppingHelper
|
|
10
|
+
|
|
11
|
+
def model_class
|
|
12
|
+
Spree::GoogleCredential
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def edit
|
|
16
|
+
@credential = current_store.google_credential || current_store.build_google_credential
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def update
|
|
20
|
+
@credential = current_store.google_credential || current_store.build_google_credential
|
|
21
|
+
|
|
22
|
+
if @credential.update(permitted_params)
|
|
23
|
+
Spree::GoogleShopping::SyncAllJob.perform_later
|
|
24
|
+
|
|
25
|
+
flash[:success] = Spree.t(:successfully_updated, resource: 'Google Settings')
|
|
26
|
+
redirect_to spree.edit_admin_google_merchant_settings_path
|
|
27
|
+
else
|
|
28
|
+
flash[:error] = "Failed to save settings. Please check the errors below."
|
|
29
|
+
render :edit
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def connect
|
|
34
|
+
redirect_uri = spree.callback_admin_google_merchant_settings_url
|
|
35
|
+
Rails.logger.info "GOOGLE CONNECT: Generated Redirect URI: #{redirect_uri}"
|
|
36
|
+
|
|
37
|
+
client = Signet::OAuth2::Client.new(
|
|
38
|
+
authorization_uri: 'https://accounts.google.com/o/oauth2/auth',
|
|
39
|
+
token_credential_uri: 'https://oauth2.googleapis.com/token',
|
|
40
|
+
client_id: ENV['GOOGLE_CLIENT_ID'],
|
|
41
|
+
client_secret: ENV['GOOGLE_CLIENT_SECRET'],
|
|
42
|
+
scope: [
|
|
43
|
+
'https://www.googleapis.com/auth/content',
|
|
44
|
+
'https://www.googleapis.com/auth/adwords',
|
|
45
|
+
'https://www.googleapis.com/auth/userinfo.email'
|
|
46
|
+
],
|
|
47
|
+
redirect_uri: redirect_uri,
|
|
48
|
+
additional_parameters: {
|
|
49
|
+
access_type: 'offline',
|
|
50
|
+
prompt: 'consent'
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
redirect_to client.authorization_uri.to_s, allow_other_host: true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def callback
|
|
57
|
+
Rails.logger.info "GOOGLE CALLBACK: Received parameters: #{params.inspect}"
|
|
58
|
+
|
|
59
|
+
if params[:error].present?
|
|
60
|
+
flash[:error] = "Google Access Denied: #{params[:error]}"
|
|
61
|
+
return redirect_to spree.edit_admin_google_merchant_settings_path
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
if params[:code].blank?
|
|
65
|
+
flash[:error] = "No code received from Google."
|
|
66
|
+
return redirect_to spree.edit_admin_google_merchant_settings_path
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
redirect_uri = spree.callback_admin_google_merchant_settings_url
|
|
70
|
+
|
|
71
|
+
client = Signet::OAuth2::Client.new(
|
|
72
|
+
token_credential_uri: 'https://oauth2.googleapis.com/token',
|
|
73
|
+
client_id: ENV['GOOGLE_CLIENT_ID'],
|
|
74
|
+
client_secret: ENV['GOOGLE_CLIENT_SECRET'],
|
|
75
|
+
redirect_uri: redirect_uri,
|
|
76
|
+
code: params[:code]
|
|
77
|
+
)
|
|
78
|
+
begin
|
|
79
|
+
response = client.fetch_access_token!
|
|
80
|
+
|
|
81
|
+
Rails.logger.info "GOOGLE TOKEN: Success! Expires in: #{response['expires_in']}"
|
|
82
|
+
|
|
83
|
+
access_token = response['access_token']
|
|
84
|
+
expires_in = response['expires_in'].to_i
|
|
85
|
+
expires_in = 3600 if expires_in <= 0
|
|
86
|
+
|
|
87
|
+
email = fetch_google_email(access_token)
|
|
88
|
+
|
|
89
|
+
cred = current_store.google_credential || current_store.build_google_credential
|
|
90
|
+
|
|
91
|
+
new_refresh_token = response['refresh_token'].presence || cred.refresh_token
|
|
92
|
+
|
|
93
|
+
cred.assign_attributes(
|
|
94
|
+
access_token: access_token,
|
|
95
|
+
refresh_token: new_refresh_token,
|
|
96
|
+
token_expires_at: Time.current + expires_in.seconds,
|
|
97
|
+
scope: response['scope'],
|
|
98
|
+
email: email,
|
|
99
|
+
store: current_store
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if cred.save(validate: false)
|
|
103
|
+
flash[:success] = "Google Account (#{email}) Connected Successfully!"
|
|
104
|
+
else
|
|
105
|
+
Rails.logger.error "DB ERROR: #{cred.errors.full_messages}"
|
|
106
|
+
flash[:error] = "Database Error: #{cred.errors.full_messages.join(', ')}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
redirect_to spree.edit_admin_google_merchant_settings_path
|
|
110
|
+
|
|
111
|
+
rescue Signet::AuthorizationError => e
|
|
112
|
+
|
|
113
|
+
current_cred = current_store.google_credential
|
|
114
|
+
if current_cred&.active? && current_cred.updated_at > 2.minutes.ago
|
|
115
|
+
Rails.logger.warn "GOOGLE: Race condition detected (invalid_grant ignored). Login was successful."
|
|
116
|
+
|
|
117
|
+
connected_email = current_cred.email || "Google Account"
|
|
118
|
+
flash[:success] = "Google Account (#{connected_email}) Connected Successfully!"
|
|
119
|
+
|
|
120
|
+
redirect_to spree.edit_admin_google_merchant_settings_path
|
|
121
|
+
return
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
Rails.logger.error "GOOGLE AUTH ERROR: #{e.message}"
|
|
125
|
+
flash[:error] = "Google Authorization Failed: #{e.message}"
|
|
126
|
+
redirect_to spree.edit_admin_google_merchant_settings_path
|
|
127
|
+
|
|
128
|
+
rescue => e
|
|
129
|
+
Rails.logger.error "GOOGLE GENERIC ERROR: #{e.message}"
|
|
130
|
+
flash[:error] = "Error: #{e.message}"
|
|
131
|
+
redirect_to spree.edit_admin_google_merchant_settings_path
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def disconnect
|
|
136
|
+
cred = current_store.google_credential
|
|
137
|
+
if cred&.destroy
|
|
138
|
+
flash[:success] = "Google Account Disconnected."
|
|
139
|
+
else
|
|
140
|
+
flash[:error] = "Could not disconnect account."
|
|
141
|
+
end
|
|
142
|
+
redirect_to spree.edit_admin_google_merchant_settings_path
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
def fetch_google_email(access_token)
|
|
148
|
+
uri = URI('https://www.googleapis.com/oauth2/v2/userinfo')
|
|
149
|
+
request = Net::HTTP::Get.new(uri)
|
|
150
|
+
request['Authorization'] = "Bearer #{access_token}"
|
|
151
|
+
request['User-Agent'] = "SpreeGoogleShopping/1.0"
|
|
152
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE) do |http|
|
|
153
|
+
http.request(request)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
157
|
+
data = JSON.parse(response.body)
|
|
158
|
+
data['email']
|
|
159
|
+
else
|
|
160
|
+
Rails.logger.warn "GOOGLE EMAIL FETCH FAILED: #{response.code} - #{response.body}"
|
|
161
|
+
"Connected Account"
|
|
162
|
+
end
|
|
163
|
+
rescue => e
|
|
164
|
+
Rails.logger.error "GOOGLE EMAIL FETCH ERROR: #{e.message}"
|
|
165
|
+
"Connected Account"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def permitted_params
|
|
169
|
+
params.require(:google_credential).permit(
|
|
170
|
+
:merchant_center_id,
|
|
171
|
+
:ad_account_id,
|
|
172
|
+
:target_country,
|
|
173
|
+
:target_currency,
|
|
174
|
+
:default_product_type,
|
|
175
|
+
:default_google_product_category,
|
|
176
|
+
:default_min_handling_time,
|
|
177
|
+
:default_max_handling_time
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
module GoogleShopping
|
|
4
|
+
class DashboardController < Spree::Admin::BaseController
|
|
5
|
+
helper Spree::Admin::BaseHelper
|
|
6
|
+
|
|
7
|
+
before_action :check_connection, only: [:index]
|
|
8
|
+
|
|
9
|
+
def index
|
|
10
|
+
@store = Spree::Store.default
|
|
11
|
+
@credential = @store.google_credential
|
|
12
|
+
|
|
13
|
+
if @credential&.active? && @credential.merchant_center_id.present?
|
|
14
|
+
service = Spree::GoogleShopping::StatusService.new(@credential)
|
|
15
|
+
@stats = service.fetch_counts
|
|
16
|
+
@last_sync = @credential.last_sync_at
|
|
17
|
+
|
|
18
|
+
if @stats[:error]
|
|
19
|
+
flash.now[:error] = "Could not fetch live stats from Google. Showing cached or empty data."
|
|
20
|
+
end
|
|
21
|
+
else
|
|
22
|
+
|
|
23
|
+
@stats = { approved: 0, limited: 0, pending: 0, disapproved: 0 }
|
|
24
|
+
@last_sync = nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def sync
|
|
29
|
+
products = Spree::Product.all
|
|
30
|
+
@store = Spree::Store.default
|
|
31
|
+
|
|
32
|
+
if products.empty?
|
|
33
|
+
flash[:error] = "No products found to sync."
|
|
34
|
+
elsif @store.google_credential.merchant_center_id.blank?
|
|
35
|
+
flash[:error] = "Please enter your Google Merchant Center ID in Settings before syncing."
|
|
36
|
+
else
|
|
37
|
+
products.each do |product|
|
|
38
|
+
Spree::GoogleShopping::SyncProductJob.perform_later(product.id)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if @store.google_credential&.merchant_center_id
|
|
42
|
+
Rails.cache.delete("google_shopping_stats_#{@store.google_credential.merchant_center_id}")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
flash[:success] = "Sync started for #{products.count} products! Statuses will update shortly."
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
redirect_to admin_google_shopping_dashboard_path
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def check_connection
|
|
54
|
+
credential = Spree::Store.default.google_credential
|
|
55
|
+
|
|
56
|
+
unless credential&.active?
|
|
57
|
+
flash[:warning] = "Please connect your Google Merchant Center account to access the dashboard."
|
|
58
|
+
redirect_to edit_admin_google_merchant_settings_path
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
module GoogleShopping
|
|
4
|
+
class IssuesController < Spree::Admin::BaseController
|
|
5
|
+
|
|
6
|
+
def index
|
|
7
|
+
@total_variants = Spree::Variant.count
|
|
8
|
+
@issues_summary = []
|
|
9
|
+
|
|
10
|
+
attributes_with_issues = Spree::GoogleVariantAttribute
|
|
11
|
+
.where.not(google_issues: nil)
|
|
12
|
+
.where.not(google_issues: "[]")
|
|
13
|
+
grouped_issues = {}
|
|
14
|
+
|
|
15
|
+
attributes_with_issues.each do |attr|
|
|
16
|
+
next if attr.google_issues.blank?
|
|
17
|
+
|
|
18
|
+
attr.google_issues.each do |issue|
|
|
19
|
+
key = issue['description']
|
|
20
|
+
if grouped_issues[key].nil?
|
|
21
|
+
variant = Spree::Variant.find_by(id: attr.variant_id)
|
|
22
|
+
product_id = variant&.product_id
|
|
23
|
+
grouped_issues[key] = {
|
|
24
|
+
short_title: issue['description'],
|
|
25
|
+
long_title: issue['detail'],
|
|
26
|
+
code: issue['code'],
|
|
27
|
+
affected_count: 0,
|
|
28
|
+
severity: issue['servability'],
|
|
29
|
+
example_product_id: product_id
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
grouped_issues[key][:affected_count] += 1
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
@issues_summary = grouped_issues.values.sort_by { |i| -i[:affected_count] }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|