shopify_graphql 1.2.7 → 2.0.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 +4 -4
- data/README.md +87 -2
- data/app/graphql/shopify_graphql/create_bulk_mutation.rb +27 -0
- data/app/graphql/shopify_graphql/create_bulk_query.rb +27 -0
- data/app/graphql/shopify_graphql/create_staged_uploads.rb +31 -0
- data/app/graphql/shopify_graphql/current_shop.rb +139 -0
- data/app/graphql/shopify_graphql/get_app_subscription.rb +1 -1
- data/app/graphql/shopify_graphql/get_bulk_operation.rb +44 -0
- data/lib/shopify_graphql/client.rb +90 -36
- data/lib/shopify_graphql/controller_concerns/webhook_verification.rb +5 -0
- data/lib/shopify_graphql/engine.rb +4 -0
- data/lib/shopify_graphql/exceptions.rb +23 -22
- data/lib/shopify_graphql/mutation.rb +2 -1
- data/lib/shopify_graphql/query.rb +2 -1
- data/lib/shopify_graphql/resource.rb +2 -1
- data/lib/shopify_graphql/version.rb +1 -1
- data/lib/shopify_graphql.rb +9 -4
- metadata +27 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe9ea3cbb7c46d86fe1281d9658cc1de370db1b306b29aa331c1c8ac9ae94f0d
|
4
|
+
data.tar.gz: ab91936f744cd3c40ee1b81952426a839e26fb3fd8b9ca55defa5248c22ed812
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ef56f4a0248e446d3063fd6ef92e13743d65a6ecefd523c8b70444f52353667e9b082efb13177a5912ad598425c6382e0ac36321aab231b46f73871e1109a06
|
7
|
+
data.tar.gz: 3c483532a0d6dc2d853a172a357a07319466bad2313937a1f57f4f359a2978affeacb80f3e3b5c3df287bc2e07565d692e831dff7564a22cc9a02a1522bab59f
|
data/README.md
CHANGED
@@ -676,12 +676,33 @@ ShopifyGraphql.handle_user_errors(response)
|
|
676
676
|
|
677
677
|
## Built-in Graphql calls
|
678
678
|
|
679
|
+
- `ShopifyGraphql::CurrentShop`:
|
680
|
+
|
681
|
+
Equivalent to `ShopifyAPI::Shop.current`. Usage example:
|
682
|
+
|
683
|
+
```rb
|
684
|
+
shop = ShopifyGraphql::CurrentShop.call
|
685
|
+
puts shop.name
|
686
|
+
```
|
687
|
+
|
688
|
+
Or with locales (requires `read_locales` scope):
|
689
|
+
|
690
|
+
```rb
|
691
|
+
shop = ShopifyGraphql::CurrentShop.call(with_locales: true)
|
692
|
+
puts shop.primary_locale
|
693
|
+
puts shop.shop_locales
|
694
|
+
```
|
695
|
+
|
679
696
|
- `ShopifyGraphql::CancelSubscription`
|
680
697
|
- `ShopifyGraphql::CreateRecurringSubscription`
|
681
698
|
- `ShopifyGraphql::CreateUsageSubscription`
|
682
699
|
- `ShopifyGraphql::GetAppSubscription`
|
683
700
|
- `ShopifyGraphql::UpsertPrivateMetafield`
|
684
701
|
- `ShopifyGraphql::DeletePrivateMetafield`
|
702
|
+
- `ShopifyGraphql::CreateBulkMutation`
|
703
|
+
- `ShopifyGraphql::CreateBulkQuery`
|
704
|
+
- `ShopifyGraphql::CreateStagedUploads`
|
705
|
+
- `ShopifyGraphql::GetBulkOperation`
|
685
706
|
|
686
707
|
Built-in wrappers are located in [`app/graphql/shopify_graphql`](/app/graphql/shopify_graphql/) folder. You can use them directly in your apps or as an example to create your own wrappers.
|
687
708
|
|
@@ -709,9 +730,73 @@ response.query_cost # => 1
|
|
709
730
|
response.points_maxed?(threshold: 100) # => false
|
710
731
|
```
|
711
732
|
|
712
|
-
##
|
733
|
+
## Custom apps
|
734
|
+
|
735
|
+
In custom apps, if you're using `shopify_app` gem, then the setup is similar public apps. Except `Shop` model which must include class method to make queries to your store:
|
736
|
+
|
737
|
+
```rb
|
738
|
+
# app/models/shop.rb
|
739
|
+
class Shop < ActiveRecord::Base
|
740
|
+
include ShopifyApp::ShopSessionStorageWithScopes
|
741
|
+
|
742
|
+
def self.system
|
743
|
+
new(
|
744
|
+
shopify_domain: "MYSHOPIFY_DOMAIN",
|
745
|
+
shopify_token: "API_ACCESS_TOKEN_FOR_CUSTOM_APP"
|
746
|
+
)
|
747
|
+
end
|
748
|
+
end
|
749
|
+
```
|
750
|
+
|
751
|
+
Using this method, you should be able to make API calls like this:
|
752
|
+
|
753
|
+
```rb
|
754
|
+
Shop.system.with_shopify_session do
|
755
|
+
GetOrder.call(id: order.shopify_gid)
|
756
|
+
end
|
757
|
+
```
|
758
|
+
|
759
|
+
If you're not using `shopify_app` gem, then you need to setup `ShopifyAPI::Context` manually:
|
760
|
+
|
761
|
+
```rb
|
762
|
+
# config/initializers/shopify_api.rb
|
763
|
+
ShopifyAPI::Context.setup(
|
764
|
+
api_key: "XXX",
|
765
|
+
api_secret_key: "XXXX",
|
766
|
+
scope: "read_orders,read_products",
|
767
|
+
is_embedded: false,
|
768
|
+
api_version: "2024-07",
|
769
|
+
is_private: true,
|
770
|
+
)
|
771
|
+
```
|
772
|
+
|
773
|
+
And create another method in Shop model to make queries to your store:
|
774
|
+
|
775
|
+
```rb
|
776
|
+
# app/models/shop.rb
|
777
|
+
def Shop
|
778
|
+
def self.with_shopify_session(&block)
|
779
|
+
ShopifyAPI::Auth::Session.temp(
|
780
|
+
shop: "MYSHOPIFY_DOMAIN",
|
781
|
+
access_token: "API_ACCESS_TOKEN_FOR_CUSTOM_APP",
|
782
|
+
&block
|
783
|
+
)
|
784
|
+
end
|
785
|
+
end
|
786
|
+
```
|
787
|
+
|
788
|
+
Using this method, you should be able to make API calls like this:
|
789
|
+
|
790
|
+
```rb
|
791
|
+
Shop.with_shopify_session do
|
792
|
+
GetOrder.call(id: order.shopify_gid)
|
793
|
+
end
|
794
|
+
```
|
795
|
+
|
796
|
+
## Graphql webhooks (deprecated)
|
713
797
|
|
714
|
-
>
|
798
|
+
> [!WARNING]
|
799
|
+
> ShopifyGraphql webhooks are deprecated and will be removed in v3.0. Please use `shopify_app` gem for handling webhooks. See [`shopify_app` documentation](https://github.com/Shopify/shopify_app/blob/main/docs/shopify_app/webhooks.md) for more details.
|
715
800
|
|
716
801
|
The gem has built-in support for Graphql webhooks (similar to `shopify_app`). To enable it add the following config to `config/initializers/shopify_app.rb`:
|
717
802
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ShopifyGraphql
|
2
|
+
class CreateBulkMutation
|
3
|
+
include Mutation
|
4
|
+
|
5
|
+
MUTATION = <<~GRAPHQL
|
6
|
+
mutation($mutation: String!, $stagedUploadPath: String!) {
|
7
|
+
bulkOperationRunMutation(mutation: $mutation, stagedUploadPath: $stagedUploadPath) {
|
8
|
+
bulkOperation {
|
9
|
+
id
|
10
|
+
status
|
11
|
+
}
|
12
|
+
userErrors {
|
13
|
+
field
|
14
|
+
message
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
18
|
+
GRAPHQL
|
19
|
+
|
20
|
+
def call(mutation:, staged_upload_path:)
|
21
|
+
response = execute(MUTATION, mutation: mutation, stagedUploadPath: staged_upload_path)
|
22
|
+
response.data = response.data.bulkOperationRunMutation
|
23
|
+
handle_user_errors(response.data)
|
24
|
+
response
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ShopifyGraphql
|
2
|
+
class CreateBulkQuery
|
3
|
+
include Mutation
|
4
|
+
|
5
|
+
MUTATION = <<~GRAPHQL
|
6
|
+
mutation($query: String!) {
|
7
|
+
bulkOperationRunQuery(query: $query) {
|
8
|
+
bulkOperation {
|
9
|
+
id
|
10
|
+
status
|
11
|
+
}
|
12
|
+
userErrors {
|
13
|
+
field
|
14
|
+
message
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
18
|
+
GRAPHQL
|
19
|
+
|
20
|
+
def call(query:)
|
21
|
+
response = execute(MUTATION, query: query)
|
22
|
+
response.data = response.data.bulkOperationRunQuery
|
23
|
+
handle_user_errors(response.data)
|
24
|
+
response
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ShopifyGraphql
|
2
|
+
class CreateStagedUploads
|
3
|
+
include Mutation
|
4
|
+
|
5
|
+
MUTATION = <<~GRAPHQL
|
6
|
+
mutation stagedUploadsCreate($input: [StagedUploadInput!]!) {
|
7
|
+
stagedUploadsCreate(input: $input) {
|
8
|
+
stagedTargets {
|
9
|
+
url
|
10
|
+
resourceUrl
|
11
|
+
parameters {
|
12
|
+
name
|
13
|
+
value
|
14
|
+
}
|
15
|
+
}
|
16
|
+
userErrors{
|
17
|
+
field,
|
18
|
+
message
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
22
|
+
GRAPHQL
|
23
|
+
|
24
|
+
def call(input:)
|
25
|
+
response = execute(MUTATION, input: input)
|
26
|
+
response.data = response.data.stagedUploadsCreate
|
27
|
+
handle_user_errors(response.data)
|
28
|
+
response
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module ShopifyGraphql
|
2
|
+
class CurrentShop
|
3
|
+
include ShopifyGraphql::Query
|
4
|
+
|
5
|
+
QUERY = <<~GRAPHQL
|
6
|
+
query {
|
7
|
+
shop {
|
8
|
+
id
|
9
|
+
name
|
10
|
+
email
|
11
|
+
contactEmail
|
12
|
+
myshopifyDomain
|
13
|
+
primaryDomain {
|
14
|
+
host
|
15
|
+
}
|
16
|
+
#CREATED_AT#
|
17
|
+
#UPDATED_AT#
|
18
|
+
#SHOP_OWNER_NAME#
|
19
|
+
currencyCode
|
20
|
+
billingAddress {
|
21
|
+
country
|
22
|
+
countryCodeV2
|
23
|
+
province
|
24
|
+
city
|
25
|
+
address1
|
26
|
+
address2
|
27
|
+
zip
|
28
|
+
latitude
|
29
|
+
longitude
|
30
|
+
phone
|
31
|
+
}
|
32
|
+
timezoneAbbreviation
|
33
|
+
ianaTimezone
|
34
|
+
plan {
|
35
|
+
displayName
|
36
|
+
}
|
37
|
+
currencyFormats {
|
38
|
+
moneyFormat
|
39
|
+
moneyInEmailsFormat
|
40
|
+
moneyWithCurrencyFormat
|
41
|
+
moneyWithCurrencyInEmailsFormat
|
42
|
+
}
|
43
|
+
weightUnit
|
44
|
+
taxShipping
|
45
|
+
taxesIncluded
|
46
|
+
setupRequired
|
47
|
+
checkoutApiSupported
|
48
|
+
transactionalSmsDisabled
|
49
|
+
enabledPresentmentCurrencies
|
50
|
+
#SMS_CONSENT#
|
51
|
+
}
|
52
|
+
#LOCALES_SUBQUERY#
|
53
|
+
}
|
54
|
+
GRAPHQL
|
55
|
+
LOCALES_SUBQUERY = <<~GRAPHQL
|
56
|
+
shopLocales {
|
57
|
+
locale
|
58
|
+
primary
|
59
|
+
}
|
60
|
+
GRAPHQL
|
61
|
+
|
62
|
+
def call(with_locales: false)
|
63
|
+
query = prepare_query(QUERY, with_locales: with_locales)
|
64
|
+
response = execute(query)
|
65
|
+
parse_data(response.data, with_locales: with_locales)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def prepare_query(query, with_locales:)
|
71
|
+
query = query.gsub("#LOCALES_SUBQUERY#", with_locales ? LOCALES_SUBQUERY : "")
|
72
|
+
if ShopifyAPI::Context.api_version.in?(%w[2024-01 2024-04 2024-07])
|
73
|
+
query.gsub!("#SHOP_OWNER_NAME#", "")
|
74
|
+
else
|
75
|
+
query.gsub!("#SHOP_OWNER_NAME#", "shopOwnerName")
|
76
|
+
end
|
77
|
+
if ShopifyAPI::Context.api_version.in?(%w[2024-01])
|
78
|
+
query.gsub!("#CREATED_AT#", "")
|
79
|
+
query.gsub!("#UPDATED_AT#", "")
|
80
|
+
query.gsub!("#SMS_CONSENT#", "")
|
81
|
+
else
|
82
|
+
query.gsub!("#CREATED_AT#", "createdAt")
|
83
|
+
query.gsub!("#UPDATED_AT#", "updatedAt")
|
84
|
+
query.gsub!("#SMS_CONSENT#", "marketingSmsConsentEnabledAtCheckout")
|
85
|
+
end
|
86
|
+
query
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_data(data, with_locales: false)
|
90
|
+
plan_display_name = ShopifyGraphql.normalize_plan_display_name(data.shop.plan.displayName)
|
91
|
+
plan_name = ShopifyGraphql::DISPLAY_NAME_TO_PLAN[plan_display_name]
|
92
|
+
response = OpenStruct.new(
|
93
|
+
id: data.shop.id.split("/").last.to_i,
|
94
|
+
name: data.shop.name,
|
95
|
+
email: data.shop.email,
|
96
|
+
customer_email: data.shop.contactEmail,
|
97
|
+
myshopify_domain: data.shop.myshopifyDomain,
|
98
|
+
domain: data.shop.primaryDomain.host,
|
99
|
+
created_at: data.shop.createdAt,
|
100
|
+
updated_at: data.shop.updatedAt,
|
101
|
+
shop_owner: data.shop.shopOwnerName,
|
102
|
+
currency: data.shop.currencyCode,
|
103
|
+
country_name: data.shop.billingAddress.country,
|
104
|
+
country: data.shop.billingAddress.countryCodeV2,
|
105
|
+
country_code: data.shop.billingAddress.countryCodeV2,
|
106
|
+
province: data.shop.billingAddress.province,
|
107
|
+
province_code: data.shop.billingAddress.provinceCode,
|
108
|
+
city: data.shop.billingAddress.city,
|
109
|
+
address1: data.shop.billingAddress.address1,
|
110
|
+
address2: data.shop.billingAddress.address2,
|
111
|
+
zip: data.shop.billingAddress.zip,
|
112
|
+
latitude: data.shop.billingAddress.latitude,
|
113
|
+
longitude: data.shop.billingAddress.longitude,
|
114
|
+
phone: data.shop.billingAddress.phone,
|
115
|
+
timezone: data.shop.timezoneAbbreviation,
|
116
|
+
iana_timezone: data.shop.ianaTimezone,
|
117
|
+
plan_name: plan_name,
|
118
|
+
plan_display_name: plan_display_name,
|
119
|
+
money_format: data.shop.currencyFormats.moneyFormat,
|
120
|
+
money_in_emails_format: data.shop.currencyFormats.moneyInEmailsFormat,
|
121
|
+
money_with_currency_format: data.shop.currencyFormats.moneyWithCurrencyFormat,
|
122
|
+
money_with_currency_in_emails_format: data.shop.currencyFormats.moneyWithCurrencyInEmailsFormat,
|
123
|
+
weight_unit: data.shop.weightUnit,
|
124
|
+
tax_shipping: data.shop.taxShipping,
|
125
|
+
taxes_included: data.shop.taxesIncluded,
|
126
|
+
setup_required: data.shop.setupRequired,
|
127
|
+
checkout_api_supported: data.shop.checkoutApiSupported,
|
128
|
+
transactional_sms_disabled: data.shop.transactionalSmsDisabled,
|
129
|
+
enabled_presentment_currencies: data.shop.enabledPresentmentCurrencies,
|
130
|
+
marketing_sms_consent_enabled_at_checkout: data.shop.marketingSmsConsentEnabledAtCheckout
|
131
|
+
)
|
132
|
+
if with_locales
|
133
|
+
response.primary_locale = data.shopLocales.find(&:primary).locale
|
134
|
+
response.shop_locales = data.shopLocales.map(&:locale)
|
135
|
+
end
|
136
|
+
response
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module ShopifyGraphql
|
2
|
+
class GetBulkOperation
|
3
|
+
include Query
|
4
|
+
|
5
|
+
QUERY = <<~GRAPHQL
|
6
|
+
query($id: ID!){
|
7
|
+
node(id: $id) {
|
8
|
+
... on BulkOperation {
|
9
|
+
id
|
10
|
+
status
|
11
|
+
errorCode
|
12
|
+
createdAt
|
13
|
+
completedAt
|
14
|
+
fileSize
|
15
|
+
url
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
GRAPHQL
|
20
|
+
|
21
|
+
def call(id:)
|
22
|
+
response = execute(QUERY, id: id)
|
23
|
+
response.data = parse_data(response.data.node)
|
24
|
+
response
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def parse_data(data)
|
30
|
+
unless data
|
31
|
+
raise ResourceNotFound.new, "BulkOperation not found"
|
32
|
+
end
|
33
|
+
|
34
|
+
OpenStruct.new(
|
35
|
+
id: data.id,
|
36
|
+
status: data.status,
|
37
|
+
error_code: data.errorCode,
|
38
|
+
created_at: Time.find_zone("UTC").parse(data.createdAt),
|
39
|
+
completed_at: data.completedAt ? Time.find_zone("UTC").parse(data.completedAt) : nil,
|
40
|
+
url: data.url
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,4 +1,29 @@
|
|
1
1
|
module ShopifyGraphql
|
2
|
+
# Mapping from deprecated plan_name to plan_display_name
|
3
|
+
PLAN_TO_DISPLAY_NAME = {
|
4
|
+
"trial" => "trial",
|
5
|
+
"frozen" => "frozen",
|
6
|
+
"fraudulent" => "cancelled",
|
7
|
+
"shopify_alumni" => "shopify_alumni",
|
8
|
+
"affiliate" => "development",
|
9
|
+
"basic" => "basic",
|
10
|
+
"professional" => "shopify",
|
11
|
+
"npo_full" => "npo_full",
|
12
|
+
"shopify_plus" => "shopify_plus",
|
13
|
+
"staff" => "staff",
|
14
|
+
"unlimited" => "advanced",
|
15
|
+
"retail" => "retail",
|
16
|
+
"cancelled" => "cancelled",
|
17
|
+
"dormant" => "pause_and_build",
|
18
|
+
"starter_2022" => "shopify_starter",
|
19
|
+
"plus_partner_sandbox" => "shopify_plus_partner_sandbox",
|
20
|
+
"paid_trial" => "extended_trial",
|
21
|
+
"partner_test" => "developer_preview",
|
22
|
+
"open_learning" => "open_learning",
|
23
|
+
"staff_business" => "staff_business"
|
24
|
+
}
|
25
|
+
DISPLAY_NAME_TO_PLAN = PLAN_TO_DISPLAY_NAME.invert
|
26
|
+
|
2
27
|
class Client
|
3
28
|
def client
|
4
29
|
@client ||= ShopifyAPI::Clients::Graphql::Admin.new(session: ShopifyAPI::Context.active_session)
|
@@ -8,11 +33,11 @@ module ShopifyGraphql
|
|
8
33
|
response = client.query(query: query, variables: variables)
|
9
34
|
Response.new(handle_response(response))
|
10
35
|
rescue ShopifyAPI::Errors::HttpResponseError => e
|
11
|
-
Response.new(handle_response(e.response))
|
36
|
+
Response.new(handle_response(e.response, e))
|
12
37
|
rescue JSON::ParserError => e
|
13
|
-
raise ServerError.new(
|
38
|
+
raise ServerError.new(response: response), "Invalid JSON response: #{e.message}"
|
14
39
|
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNREFUSED, Errno::ENETUNREACH, Net::ReadTimeout, Net::OpenTimeout, OpenSSL::SSL::SSLError, EOFError => e
|
15
|
-
raise ServerError.new(
|
40
|
+
raise ServerError.new(response: response), "Network error: #{e.message}"
|
16
41
|
rescue => e
|
17
42
|
if (defined?(Socket::ResolutionError) and e.is_a?(Socket::ResolutionError))
|
18
43
|
raise ServerError.new(response: response), "Network error: #{e.message}"
|
@@ -29,78 +54,107 @@ module ShopifyGraphql
|
|
29
54
|
end
|
30
55
|
end
|
31
56
|
|
32
|
-
def handle_response(response)
|
57
|
+
def handle_response(response, error = nil)
|
33
58
|
case response.code
|
34
59
|
when 200..400
|
35
|
-
handle_graphql_errors(
|
60
|
+
handle_graphql_errors(response)
|
36
61
|
when 400
|
37
|
-
raise BadRequest.new(
|
62
|
+
raise BadRequest.new(response: response), error.message
|
38
63
|
when 401
|
39
|
-
raise UnauthorizedAccess.new(
|
64
|
+
raise UnauthorizedAccess.new(response: response), error.message
|
40
65
|
when 402
|
41
|
-
raise PaymentRequired.new(
|
66
|
+
raise PaymentRequired.new(response: response), error.message
|
42
67
|
when 403
|
43
|
-
raise ForbiddenAccess.new(
|
68
|
+
raise ForbiddenAccess.new(response: response), error.message
|
44
69
|
when 404
|
45
|
-
raise ResourceNotFound.new(
|
70
|
+
raise ResourceNotFound.new(response: response), error.message
|
46
71
|
when 405
|
47
|
-
raise MethodNotAllowed.new(
|
72
|
+
raise MethodNotAllowed.new(response: response), error.message
|
48
73
|
when 409
|
49
|
-
raise ResourceConflict.new(
|
74
|
+
raise ResourceConflict.new(response: response), error.message
|
50
75
|
when 410
|
51
|
-
raise ResourceGone.new(
|
76
|
+
raise ResourceGone.new(response: response), error.message
|
52
77
|
when 412
|
53
|
-
raise PreconditionFailed.new(
|
78
|
+
raise PreconditionFailed.new(response: response), error.message
|
54
79
|
when 422
|
55
|
-
raise ResourceInvalid.new(
|
80
|
+
raise ResourceInvalid.new(response: response), error.message
|
56
81
|
when 423
|
57
|
-
raise ShopLocked.new(
|
82
|
+
raise ShopLocked.new(response: response), error.message
|
58
83
|
when 429, 430
|
59
|
-
raise TooManyRequests.new(
|
84
|
+
raise TooManyRequests.new(response: response), error.message
|
60
85
|
when 401...500
|
61
|
-
raise ClientError.new(
|
86
|
+
raise ClientError.new(response: response), error.message
|
62
87
|
when 500...600
|
63
|
-
raise ServerError.new(
|
88
|
+
raise ServerError.new(response: response), error.message
|
64
89
|
else
|
65
|
-
raise ConnectionError.new(
|
90
|
+
raise ConnectionError.new(response: response), error.message
|
66
91
|
end
|
67
92
|
end
|
68
93
|
|
69
94
|
def handle_graphql_errors(response)
|
70
|
-
|
95
|
+
parsed_body = parsed_body(response)
|
96
|
+
if parsed_body.errors.nil? || parsed_body.errors.empty?
|
97
|
+
return parsed_body(response)
|
98
|
+
end
|
71
99
|
|
72
|
-
error =
|
73
|
-
error_message = error.message
|
100
|
+
error = parsed_body.errors.first
|
74
101
|
error_code = error.extensions&.code
|
75
|
-
|
102
|
+
error_message = generate_error_message(
|
103
|
+
message: error.message,
|
104
|
+
code: error_code,
|
105
|
+
doc: error.extensions&.documentation
|
106
|
+
)
|
76
107
|
|
77
|
-
case error_code
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
108
|
+
exception = case error_code
|
109
|
+
when "THROTTLED"
|
110
|
+
TooManyRequests.new(response: response)
|
111
|
+
when "INTERNAL_SERVER_ERROR"
|
112
|
+
ServerError.new(response: response)
|
113
|
+
else
|
114
|
+
ConnectionError.new(response: response)
|
115
|
+
end
|
116
|
+
exception.error_code = error_code
|
117
|
+
raise exception, error_message
|
85
118
|
end
|
86
119
|
|
87
120
|
def handle_user_errors(response)
|
88
121
|
return response if response.userErrors.blank?
|
89
122
|
|
90
123
|
error = response.userErrors.first
|
91
|
-
error_message = error.message
|
92
|
-
error_fields = error.field
|
93
124
|
error_code = error.code
|
125
|
+
error_message = generate_error_message(
|
126
|
+
message: error.message,
|
127
|
+
code: error_code,
|
128
|
+
fields: error.field,
|
129
|
+
)
|
94
130
|
|
95
|
-
|
131
|
+
exception = UserError.new(response: response)
|
132
|
+
exception.error_code = error_code
|
133
|
+
exception.fields = error.field
|
134
|
+
raise exception, error_message
|
135
|
+
end
|
136
|
+
|
137
|
+
def generate_error_message(message: nil, code: nil, doc: nil, fields: nil)
|
138
|
+
string = "Failed.".dup
|
139
|
+
string << " Response code = #{code}." if code
|
140
|
+
string << " Response message = #{message}.".gsub("..", ".") if message
|
141
|
+
string << " Documentation = #{doc}." if doc
|
142
|
+
string << " Fields = #{fields}." if fields
|
143
|
+
string
|
96
144
|
end
|
97
145
|
end
|
98
146
|
|
99
147
|
class << self
|
100
|
-
|
148
|
+
extend Forwardable
|
149
|
+
def_delegators :client, :execute, :handle_user_errors
|
101
150
|
|
102
151
|
def client
|
103
152
|
Client.new
|
104
153
|
end
|
154
|
+
|
155
|
+
def normalize_plan_display_name(plan_display_name)
|
156
|
+
return if plan_display_name.blank?
|
157
|
+
plan_display_name.parameterize(separator: "_")
|
158
|
+
end
|
105
159
|
end
|
106
160
|
end
|
@@ -4,12 +4,17 @@ module ShopifyGraphql
|
|
4
4
|
include ShopifyGraphql::PayloadVerification
|
5
5
|
|
6
6
|
included do
|
7
|
+
before_action :deprecate_webhooks
|
7
8
|
skip_before_action :verify_authenticity_token, raise: false
|
8
9
|
before_action :verify_request
|
9
10
|
end
|
10
11
|
|
11
12
|
private
|
12
13
|
|
14
|
+
def deprecate_webhooks
|
15
|
+
ShopifyGraphql.deprecator.warn("ShopifyGraphql webhooks are deprecated and will be removed in v3.0. Please use shopify_app gem for handling webhooks.")
|
16
|
+
end
|
17
|
+
|
13
18
|
def verify_request
|
14
19
|
data = request.raw_post
|
15
20
|
return head(:unauthorized) unless hmac_valid?(data)
|
@@ -1,22 +1,23 @@
|
|
1
1
|
module ShopifyGraphql
|
2
|
-
class ConnectionError <
|
3
|
-
|
4
|
-
|
5
|
-
def initialize(response
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
2
|
+
class ConnectionError < ShopifyAPI::Errors::HttpResponseError
|
3
|
+
attr_accessor :error_code, :fields
|
4
|
+
|
5
|
+
def initialize(response: nil)
|
6
|
+
unless response
|
7
|
+
empty_response = ShopifyAPI::Clients::HttpResponse.new(code: 200, headers: {}, body: "")
|
8
|
+
super(response: empty_response) and return
|
9
|
+
end
|
10
|
+
|
11
|
+
if response.is_a?(ShopifyAPI::Clients::HttpResponse)
|
12
|
+
super(response: response)
|
13
|
+
else
|
14
|
+
response = ShopifyAPI::Clients::HttpResponse.new(
|
15
|
+
code: 200,
|
16
|
+
headers: {},
|
17
|
+
body: response
|
18
|
+
)
|
19
|
+
super(response: response)
|
20
|
+
end
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
@@ -68,11 +69,11 @@ module ShopifyGraphql
|
|
68
69
|
class TooManyRequests < ClientError # :nodoc:
|
69
70
|
end
|
70
71
|
|
71
|
-
# Graphql userErrors
|
72
|
-
class UserError < ClientError # :nodoc:
|
73
|
-
end
|
74
|
-
|
75
72
|
# 5xx Server Error
|
76
73
|
class ServerError < ConnectionError
|
77
74
|
end
|
75
|
+
|
76
|
+
# Custom error for Graphql userErrors handling
|
77
|
+
class UserError < ConnectionError
|
78
|
+
end
|
78
79
|
end
|
data/lib/shopify_graphql.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require 'shopify_api'
|
2
|
+
require 'active_support'
|
2
3
|
|
3
4
|
require 'shopify_graphql/client'
|
4
5
|
require 'shopify_graphql/configuration'
|
5
|
-
|
6
|
+
if defined?(Rails)
|
7
|
+
require 'shopify_graphql/engine'
|
8
|
+
end
|
6
9
|
require 'shopify_graphql/exceptions'
|
7
10
|
require 'shopify_graphql/mutation'
|
8
11
|
require 'shopify_graphql/query'
|
@@ -15,9 +18,11 @@ require 'shopify_graphql/controller_concerns/payload_verification'
|
|
15
18
|
require 'shopify_graphql/controller_concerns/webhook_verification'
|
16
19
|
|
17
20
|
# jobs
|
18
|
-
|
19
|
-
require 'shopify_graphql/jobs/
|
20
|
-
require 'shopify_graphql/jobs/
|
21
|
+
if defined?(Rails)
|
22
|
+
require 'shopify_graphql/jobs/create_webhooks_job'
|
23
|
+
require 'shopify_graphql/jobs/destroy_webhooks_job'
|
24
|
+
require 'shopify_graphql/jobs/update_webhooks_job'
|
25
|
+
end
|
21
26
|
|
22
27
|
# managers
|
23
28
|
require 'shopify_graphql/managers/webhooks_manager'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shopify_graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kirill Platonov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -16,28 +16,42 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 6.
|
19
|
+
version: 6.1.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 6.
|
26
|
+
version: 6.1.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 6.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 6.1.0
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: shopify_api
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - ">="
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
47
|
+
version: '13.4'
|
34
48
|
type: :runtime
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
52
|
- - ">="
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
54
|
+
version: '13.4'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: shopify_app
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -64,10 +78,15 @@ files:
|
|
64
78
|
- app/controllers/shopify_graphql/graphql_webhooks_controller.rb
|
65
79
|
- app/graphql/shopify_graphql/app_subscription_fields.rb
|
66
80
|
- app/graphql/shopify_graphql/cancel_subscription.rb
|
81
|
+
- app/graphql/shopify_graphql/create_bulk_mutation.rb
|
82
|
+
- app/graphql/shopify_graphql/create_bulk_query.rb
|
67
83
|
- app/graphql/shopify_graphql/create_recurring_subscription.rb
|
84
|
+
- app/graphql/shopify_graphql/create_staged_uploads.rb
|
68
85
|
- app/graphql/shopify_graphql/create_usage_subscription.rb
|
86
|
+
- app/graphql/shopify_graphql/current_shop.rb
|
69
87
|
- app/graphql/shopify_graphql/delete_private_metafield.rb
|
70
88
|
- app/graphql/shopify_graphql/get_app_subscription.rb
|
89
|
+
- app/graphql/shopify_graphql/get_bulk_operation.rb
|
71
90
|
- app/graphql/shopify_graphql/upsert_private_metafield.rb
|
72
91
|
- config/routes.rb
|
73
92
|
- lib/shopify_graphql.rb
|
@@ -102,14 +121,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
102
121
|
requirements:
|
103
122
|
- - ">="
|
104
123
|
- !ruby/object:Gem::Version
|
105
|
-
version:
|
124
|
+
version: '3.0'
|
106
125
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
126
|
requirements:
|
108
127
|
- - ">="
|
109
128
|
- !ruby/object:Gem::Version
|
110
129
|
version: '0'
|
111
130
|
requirements: []
|
112
|
-
rubygems_version: 3.
|
131
|
+
rubygems_version: 3.5.11
|
113
132
|
signing_key:
|
114
133
|
specification_version: 4
|
115
134
|
summary: Less painful way to work with Shopify Graphql API in Ruby.
|