pindo 5.12.2 → 5.13.2

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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/base/git_handler.rb +692 -0
  3. data/lib/pindo/command/android/autobuild.rb +2 -2
  4. data/lib/pindo/command/android.rb +0 -1
  5. data/lib/pindo/command/appstore/adhocbuild.rb +258 -311
  6. data/lib/pindo/command/appstore/autobuild.rb +203 -0
  7. data/lib/pindo/command/appstore/autoresign.rb +35 -17
  8. data/lib/pindo/command/appstore/bundleid.rb +120 -0
  9. data/lib/pindo/command/appstore/cert.rb +212 -0
  10. data/lib/pindo/command/appstore/configproj.rb +81 -0
  11. data/lib/pindo/command/{deploy → appstore}/getitcinfo.rb +76 -91
  12. data/lib/pindo/command/appstore/iap.rb +788 -24
  13. data/lib/pindo/command/appstore/initconfig.rb +105 -0
  14. data/lib/pindo/command/appstore/itcapp.rb +95 -13
  15. data/lib/pindo/command/{deploy → appstore}/itcinfo.rb +90 -118
  16. data/lib/pindo/command/appstore/pem.rb +136 -0
  17. data/lib/pindo/command/appstore/pullconfig.rb +99 -0
  18. data/lib/pindo/command/appstore/quswark.rb +87 -0
  19. data/lib/pindo/command/appstore/quswauth.rb +67 -0
  20. data/lib/pindo/command/appstore/tag.rb +77 -0
  21. data/lib/pindo/command/appstore.rb +13 -1
  22. data/lib/pindo/command/env/quarkenv.rb +11 -13
  23. data/lib/pindo/command/env/swarkenv.rb +11 -16
  24. data/lib/pindo/command/ios/autobuild.rb +64 -43
  25. data/lib/pindo/command/ios/autoresign.rb +34 -19
  26. data/lib/pindo/command/ios/build.rb +10 -7
  27. data/lib/pindo/command/ios/cert.rb +27 -20
  28. data/lib/pindo/command/jps/upload.rb +3 -3
  29. data/lib/pindo/command/unity/autobuild.rb +2 -2
  30. data/lib/pindo/command/utils/clearcert.rb +2 -17
  31. data/lib/pindo/command/{deploy → utils}/fabric.rb +13 -13
  32. data/lib/pindo/command/utils/renewcert.rb +62 -38
  33. data/lib/pindo/command/utils/renewproj.rb +0 -3
  34. data/lib/pindo/command/{deploy → utils}/updateconfig.rb +6 -7
  35. data/lib/pindo/command/utils.rb +2 -0
  36. data/lib/pindo/command/web/autobuild.rb +2 -2
  37. data/lib/pindo/command.rb +30 -3
  38. data/lib/pindo/config/build_info_manager.rb +176 -0
  39. data/lib/pindo/config/ios_config_parser.rb +404 -0
  40. data/lib/pindo/module/android/android_build_helper.rb +110 -0
  41. data/lib/pindo/module/android/android_config_helper.rb +9 -5
  42. data/lib/pindo/module/android/gradle_helper.rb +88 -0
  43. data/lib/pindo/module/appstore/bundleid_helper.rb +349 -0
  44. data/lib/pindo/module/appstore/itcapp_helper.rb +228 -0
  45. data/lib/pindo/module/build/build_helper.rb +12 -0
  46. data/lib/pindo/module/build/swark_helper.rb +116 -77
  47. data/lib/pindo/module/cert/cert_helper.rb +74 -0
  48. data/lib/pindo/module/cert/pem_helper.rb +72 -0
  49. data/lib/pindo/module/cert/{xcodecerthelper.rb → xcode_cert_helper.rb} +208 -6
  50. data/lib/pindo/module/task/model/appstore/appstore_task.rb +18 -0
  51. data/lib/pindo/module/task/model/appstore/appstore_upload_ipa_task.rb +151 -0
  52. data/lib/pindo/module/task/model/appstore/appstore_upload_metadata_task.rb +250 -0
  53. data/lib/pindo/module/task/model/appstore/appstore_upload_screenshot_task.rb +276 -0
  54. data/lib/pindo/module/task/model/build/android_build_adhoc_task.rb +210 -0
  55. data/lib/pindo/module/task/model/build/{android_dev_build_task.rb → android_build_dev_task.rb} +2 -2
  56. data/lib/pindo/module/task/model/build/android_build_gplay_task.rb +210 -0
  57. data/lib/pindo/module/task/model/build/android_build_task.rb +13 -0
  58. data/lib/pindo/module/task/model/build/ios_build_adhoc_task.rb +197 -0
  59. data/lib/pindo/module/task/model/build/ios_build_appstore_task.rb +367 -0
  60. data/lib/pindo/module/task/model/build/{ios_dev_build_task.rb → ios_build_dev_task.rb} +37 -27
  61. data/lib/pindo/module/task/model/build/ios_build_task.rb +13 -0
  62. data/lib/pindo/module/task/model/build/{web_dev_build_task.rb → web_build_dev_task.rb} +1 -1
  63. data/lib/pindo/module/task/model/build_task.rb +15 -12
  64. data/lib/pindo/module/task/model/jps_resign_task.rb +185 -0
  65. data/lib/pindo/module/task/model/{upload_task.rb → jps_upload_task.rb} +3 -3
  66. data/lib/pindo/module/task/model/unity_export_task.rb +3 -1
  67. data/lib/pindo/module/task/pindo_task.rb +19 -10
  68. data/lib/pindo/module/unity/unity_helper.rb +2 -1
  69. data/lib/pindo/module/xcode/ipa_resign_helper.rb +210 -0
  70. data/lib/pindo/module/xcode/{xcodeappconfig.rb → xcode_app_config.rb} +79 -0
  71. data/lib/pindo/module/xcode/xcode_build_config.rb +152 -17
  72. data/lib/pindo/module/xcode/xcode_build_helper.rb +151 -1
  73. data/lib/pindo/module/xcode/xcode_swark_helper.rb +341 -0
  74. data/lib/pindo/options/core/global_options_state.rb +268 -0
  75. data/lib/pindo/options/core/option_configuration.rb +206 -0
  76. data/lib/pindo/options/core/option_initializer.rb +51 -0
  77. data/lib/pindo/options/core/option_item.rb +144 -0
  78. data/lib/pindo/options/core/option_value_parser.rb +54 -0
  79. data/lib/pindo/options/groups/build_options.rb +60 -0
  80. data/lib/pindo/options/groups/jps_options.rb +70 -0
  81. data/lib/pindo/options/groups/option_group.rb +73 -0
  82. data/lib/pindo/options/helpers/bundleid_selector.rb +103 -0
  83. data/lib/pindo/options/options.rb +14 -0
  84. data/lib/pindo/version.rb +1 -1
  85. metadata +49 -41
  86. data/lib/pindo/command/android/build.rb +0 -186
  87. data/lib/pindo/command/appstore/import.rb +0 -259
  88. data/lib/pindo/command/deploy/build.rb +0 -250
  89. data/lib/pindo/command/deploy/bundleid.rb +0 -259
  90. data/lib/pindo/command/deploy/cert.rb +0 -202
  91. data/lib/pindo/command/deploy/check.rb +0 -93
  92. data/lib/pindo/command/deploy/configproj.rb +0 -120
  93. data/lib/pindo/command/deploy/confusecode.rb +0 -262
  94. data/lib/pindo/command/deploy/confuseproj.rb +0 -122
  95. data/lib/pindo/command/deploy/iap.rb +0 -826
  96. data/lib/pindo/command/deploy/initconfig.rb +0 -138
  97. data/lib/pindo/command/deploy/itcapp.rb +0 -146
  98. data/lib/pindo/command/deploy/pem.rb +0 -55
  99. data/lib/pindo/command/deploy/pullconfig.rb +0 -56
  100. data/lib/pindo/command/deploy/pushconfig.rb +0 -93
  101. data/lib/pindo/command/deploy/quswark.rb +0 -156
  102. data/lib/pindo/command/deploy/quswauth.rb +0 -76
  103. data/lib/pindo/command/deploy/reportbug.rb +0 -145
  104. data/lib/pindo/command/deploy/resign.rb +0 -300
  105. data/lib/pindo/command/deploy/tag.rb +0 -108
  106. data/lib/pindo/command/deploy/uploadipa.rb +0 -73
  107. data/lib/pindo/command/deploy.rb +0 -42
  108. data/lib/pindo/command/dev/autobuild.rb +0 -117
  109. data/lib/pindo/command/dev/build.rb +0 -94
  110. data/lib/pindo/command/dev/debug.rb +0 -112
  111. data/lib/pindo/module/task/model/build/android_release_build_task.rb +0 -29
  112. data/lib/pindo/module/task/model/build/ios_adhoc_build_task.rb +0 -53
  113. data/lib/pindo/module/task/model/build/ios_release_build_task.rb +0 -53
  114. data/lib/pindo/options/appconfigoptions.rb +0 -24
  115. data/lib/pindo/options/deployoptions.rb +0 -372
@@ -1,56 +1,820 @@
1
+ require 'app_store_dev_api'
2
+ require 'json'
3
+ require 'faraday'
1
4
  require 'highline/import'
2
- require 'xcodeproj'
3
- require 'find'
4
- require 'fileutils'
5
+ require 'pindo/config/ios_config_parser'
5
6
 
6
7
  module Pindo
7
8
  class Command
8
9
  class Appstore < Command
9
10
  class Iap < Appstore
10
11
 
12
+ include AppStoreInAppPurchase
11
13
 
12
- self.summary = 'Appstore中iap相关操作'
14
+ self.summary = '创建、修改AppStore中的Iap购买项,生成boss的iap购买项'
13
15
 
14
16
  self.description = <<-DESC
15
- Appstore中iap相关操作。
17
+ 创建、修改AppStore中的Iap购买项,生成boss的iap购买项
16
18
 
17
- 支持功能:
19
+ 支持功能:
20
+ - 检查并验证App Store内购项目配置
21
+ - 从App Store下载应用的内购信息
22
+ - 生成Boss系统所需的内购配置文件
18
23
 
19
- * 创建IAP购买项
24
+ 使用示例:
25
+ pindo appstore iap --check
26
+ pindo appstore iap --download
27
+ pindo appstore iap --boss
28
+ pindo appstore iap --download --boss
29
+ DESC
20
30
 
21
- * 更新IAP配置
31
+ self.arguments = [
32
+ ]
22
33
 
23
- * 管理订阅项目
34
+ def self.options
35
+ [
36
+ ['--check', '检查iap信息'],
37
+ ['--download', '从appstore下载app的iap信息'],
38
+ ['--boss', '生成boss的iap信息'],
24
39
 
25
- 使用示例:
40
+ ].concat(super)
41
+ end
26
42
 
27
- $ pindo appstore iap # 默认创建iap购买项
43
+ def validate!
44
+ super
28
45
 
29
- $ pindo appstore iap config.json # 使用指定配置文件
30
- DESC
46
+ pindo_dir = File.expand_path(pindo_single_config.pindo_dir)
47
+ config_repo_dir = File.join(pindo_dir, @bundle_id)
48
+ @iapconfig_dir = File.join(config_repo_dir, "fastlane/iap")
31
49
 
32
- self.arguments = [
33
- CLAide::Argument.new('path/to/config.json', false),
34
- ]
50
+ puts "iap dir: " + @iapconfig_dir
51
+ puts
35
52
 
36
- def self.options
37
- [
38
- ['--download', '下载app的所有购买项,用法:pindo appstore iap --download'],
39
- ].concat(super)
40
53
  end
41
54
 
42
55
  def initialize(argv)
43
56
 
44
- @args_download_name = argv.option('download')
45
-
46
- super
47
- end
57
+ @check_flag = argv.flag?('check', false)
58
+ @download_flag = argv.flag?('download', false)
59
+ @boss_flag = argv.flag?('boss', false)
48
60
 
61
+ super
62
+ end
49
63
 
50
64
  def run
51
65
 
66
+
67
+ # 加载配置
68
+ project_dir = Dir.pwd
69
+ config_file = File.join(project_dir, "config.json")
70
+
71
+ config_parser = Pindo::IosConfigParser.instance
72
+ config_parser.load_config(config_file: config_file)
73
+ @config_json = config_parser.config_json
74
+ @bundle_id = config_parser.bundle_id
75
+ @apple_id = config_parser.apple_id
76
+
77
+ if @config_json.nil? || @config_json.empty?
78
+ raise Informative, "配置未加载,请检查配置文件: #{config_file}"
79
+ end
80
+
81
+ if @bundle_id.nil? || @bundle_id.empty?
82
+ raise Informative, "无法从配置文件中获取 Bundle ID"
83
+ end
84
+
85
+ if @apple_id.nil? || @apple_id.empty?
86
+ raise Informative, "无法从配置文件中获取 Apple ID"
87
+ end
88
+
89
+ if @download_flag
90
+
91
+ login_api_key(apple_id: @apple_id)
92
+ Spaceship::Tunes.login(@apple_id)
93
+
94
+ appstore_iap_file = File.join(@iapconfig_dir, "iap_appstore_download.json")
95
+ download_appstore_iap(appstore_iap_file: appstore_iap_file)
96
+ elsif @boss_flag
97
+
98
+ puts "one === first auto vip for boss =========\n\n\n"
99
+ appstore_iap_file = File.join(@iapconfig_dir, "one/iap_appstore.json")
100
+ produce_boss_iap(appstore_iap_file: appstore_iap_file, vip_mode: "autovip")
101
+
102
+ puts "two === second fancy vip for boss =========\n\n\n"
103
+ appstore_iap_file = File.join(@iapconfig_dir, "two/iap_appstore.json")
104
+ produce_boss_iap(appstore_iap_file: appstore_iap_file, vip_mode: "fancyvip")
105
+
106
+ puts "three === third funny vip for boss =========\n\n\n"
107
+ appstore_iap_file = File.join(@iapconfig_dir, "three/iap_appstore.json")
108
+ produce_boss_iap(appstore_iap_file: appstore_iap_file, vip_mode: "funnyvip")
109
+
110
+ else
111
+
112
+ login_api_key(apple_id: @apple_id)
113
+ Spaceship::Tunes.login(@apple_id)
114
+
115
+ create_iap_tester()
116
+ appstore_iap_file = File.join(@iapconfig_dir, "iap_appstore.json")
117
+ create_appstore_iap(appstore_iap_file: appstore_iap_file)
118
+ end
119
+ end
120
+
121
+ def download_appstore_iap(appstore_iap_file: nil)
122
+ puts "正在下载appstore iap 信息 ..."
123
+
124
+ app_list_response = @app_store_connect.list_apps(filter: {
125
+ bundle_id: @bundle_id
126
+ })
127
+
128
+ current_app = nil
129
+ app_list_response[:data].each do |app_info|
130
+ if app_info[:attributes][:bundle_id].to_s.eql?(@bundle_id)
131
+ current_app = app_info
132
+ end
133
+ end
134
+
135
+ app_id = current_app[:id]
136
+
137
+ puts " "
138
+ puts "app_id : #{app_id} +++++++++"
139
+
140
+ in_app_purchase_json = {}
141
+
142
+ puts "正在下载消耗型购买项..."
143
+ in_app_purchase_type = "CONSUMABLE"
144
+ in_app_purchase_json["consumable"] = get_consumable_iap_items(appstore_client: @app_store_connect, app_id: app_id, in_app_purchase_type: in_app_purchase_type) || []
145
+
146
+ puts "正在下载非消耗型购买项..."
147
+ in_app_purchase_type = "NON_CONSUMABLE"
148
+ in_app_purchase_json["non_consumable"] = get_consumable_iap_items(appstore_client: @app_store_connect, app_id: app_id, in_app_purchase_type: in_app_purchase_type) || []
149
+
150
+ puts "正在下载非自动订阅购买项..."
151
+ in_app_purchase_type = "NON_RENEWING_SUBSCRIPTION"
152
+ in_app_purchase_json["non_renew"] = get_consumable_iap_items(appstore_client: @app_store_connect, app_id: app_id, in_app_purchase_type: in_app_purchase_type) || []
153
+
154
+ puts "正在下载自动订阅购买项..."
155
+ subscription_group_items = get_subscriptions_groups(appstore_client: @app_store_connect, app_id: app_id)
156
+ in_app_purchase_json["auto_renew"] = subscription_group_items
157
+
158
+ puts JSON.pretty_generate(in_app_purchase_json)
159
+ File.open(appstore_iap_file, "w") do |f|
160
+ f.write(JSON.pretty_generate(in_app_purchase_json))
161
+ end
162
+
163
+ pindo_dir = File.expand_path(pindo_single_config.pindo_dir)
164
+ config_repo_dir = File.join(pindo_dir, @bundle_id)
165
+ git_addpush_repo(path: config_repo_dir, message: "download app iap", commit_file_params: ["fastlane/iap/iap_appstore_download.json"])
166
+
167
+ update_app_share_secrets(app_id: app_id, need_create: false)
168
+ end
169
+
170
+ def produce_boss_iap(appstore_iap_file: nil, vip_mode: "autovip")
171
+
172
+ if !File.exist?(appstore_iap_file)
173
+
174
+ return
175
+ end
176
+
177
+ appstore_iap_file = File.expand_path(appstore_iap_file)
178
+ appstore_iap_json = JSON.parse(File.read(appstore_iap_file))
179
+
180
+ boss_iap_file = File.join(File.dirname(appstore_iap_file), "iap_boss.json")
181
+
182
+
183
+ if !appstore_iap_json.nil? && !appstore_iap_json["auto_renew"].nil?
184
+
185
+ appstore_iap_json["auto_renew"].each do |vip_group_item|
186
+ if !vip_group_item.nil?
187
+
188
+ appstore_iap_items = vip_group_item["items"] || vip_group_item["subscription_items"]
189
+
190
+ boss_iap_json = {
191
+ "client_vip" => {
192
+ "situation_list" => [],
193
+ "style_list" => []
194
+ },
195
+ "offer_map" => {}
196
+ }
197
+
198
+ situation_list_item = {
199
+ "situation" => "launch",
200
+ "style" => "tribble_style"
201
+ }
202
+ style_list_item = {
203
+ "style" => "tribble_style",
204
+ "best_deal" => "offer.id." + vip_mode + "0",
205
+ "offer" => []
206
+ }
207
+
208
+ if !appstore_iap_items.nil? && appstore_iap_items.size > 0
209
+ appstore_iap_items.each do |appstore_iap_item|
210
+ boss_iap_item = {}
211
+ boss_iap_item["offer_id"] = "offer.id." + vip_mode
212
+ boss_iap_item["kind"] = "vip"
213
+ boss_iap_item["way"] = "iap"
214
+ boss_iap_item["type"] = "auto_renewable_subscription"
215
+ boss_iap_item["subscription"] = {}
216
+
217
+ if !appstore_iap_item["subscription_free_trial"].nil? && appstore_iap_item["subscription_free_trial"].eql?("3d")
218
+ boss_iap_item["free_days"] = 3
219
+ end
220
+ boss_iap_item["iap"] = {}
221
+ boss_iap_item["iap"]["price"] = appstore_iap_item["price"]
222
+
223
+ if appstore_iap_item["subscription_duration"].include?("w")
224
+ boss_iap_item["offer_id"] = boss_iap_item["offer_id"] + "0"
225
+ boss_iap_item["subscription"]["period_type"] = "week"
226
+ boss_iap_item["subscription"]["period"] = appstore_iap_item["subscription_duration"].gsub('w', '').to_i
227
+ elsif appstore_iap_item["subscription_duration"].include?("m")
228
+ boss_iap_item["offer_id"] = boss_iap_item["offer_id"] + "1"
229
+ boss_iap_item["subscription"]["period_type"] = "month"
230
+ boss_iap_item["subscription"]["period"] = appstore_iap_item["subscription_duration"].gsub('m', '').to_i
231
+ boss_iap_item["iap"]["discount"] = 15
232
+ elsif appstore_iap_item["subscription_duration"].include?("y")
233
+ boss_iap_item["offer_id"] = boss_iap_item["offer_id"] + "2"
234
+ boss_iap_item["subscription"]["period_type"] = "year"
235
+ boss_iap_item["subscription"]["period"] = appstore_iap_item["subscription_duration"].gsub('y', '').to_i
236
+ boss_iap_item["iap"]["discount"] = 50
237
+ end
238
+
239
+ if !boss_iap_item["iap"]["price"].to_s.include?("$")
240
+ boss_iap_item["iap"]["price"] = "$" + boss_iap_item["iap"]["price"].to_s
241
+ end
242
+
243
+ boss_iap_item["iap"]["product_id"] = appstore_iap_item["product_id"]
244
+ if boss_iap_item["iap"]["product_id"].include?("PRODUCT_BUNDLE_IDENTIFIER")
245
+ temp_product_id = boss_iap_item["iap"]["product_id"]
246
+ temp_product_id = temp_product_id.gsub(/PRODUCT_BUNDLE_IDENTIFIER/, @bundle_id);
247
+ boss_iap_item["iap"]["product_id"] = temp_product_id
248
+ end
249
+
250
+ boss_iap_json["offer_map"][boss_iap_item["offer_id"]] = boss_iap_item
251
+ style_list_item["offer"] << boss_iap_item["offer_id"]
252
+ end
253
+
254
+ if style_list_item["offer"].size == 3
255
+ situation_list_item["style"] = "tribble_style"
256
+ style_list_item["style"] = "tribble_style"
257
+ elsif style_list_item["offer"].size == 2
258
+ situation_list_item["style"] = "double_style"
259
+ style_list_item["style"] = "double_style"
260
+ elsif style_list_item["offer"].size == 1
261
+ situation_list_item["style"] = "single_style"
262
+ style_list_item["style"] = "single_style"
263
+ end
264
+
265
+ boss_iap_json["client_vip"]["situation_list"] << situation_list_item
266
+ boss_iap_json["client_vip"]["style_list"] << style_list_item
267
+ end
268
+
269
+ puts JSON.pretty_generate(boss_iap_json)
270
+ puts "\n\n\n"
271
+ File.open(boss_iap_file, "w") do |f|
272
+ f.write(JSON.pretty_generate(boss_iap_json))
273
+ end
274
+
275
+
276
+ end
277
+ end
278
+ end
279
+
280
+ end
281
+
282
+
283
+ def update_app_share_secrets(app_id: nil, need_create: true)
284
+
285
+ app_secret_id = get_app_share_secrets(app_id: app_id, need_create: need_create)
286
+ if !app_secret_id.nil?
287
+ puts
288
+ puts "App Shared Secret: #{app_secret_id}"
289
+ puts
290
+ pindo_dir = File.expand_path(pindo_single_config.pindo_dir)
291
+ config_repo_dir = File.join(pindo_dir, @bundle_id)
292
+
293
+ app_origin_config = File.join(config_repo_dir, "config.json")
294
+ config_json = JSON.parse(File.read(app_origin_config))
295
+ config_json['app_info']['appstore_iap_secret'] = app_secret_id
296
+ File.open(app_origin_config, "w") do |file|
297
+ file.write(JSON.pretty_generate(config_json))
298
+ file.close
299
+ end
300
+
301
+ project_dir = Dir.pwd
302
+ project_config_file = File.join(project_dir, "config.json")
303
+ config_json = JSON.parse(File.read(project_config_file))
304
+ config_json['app_info']['appstore_iap_secret'] = app_secret_id
305
+ File.open(project_config_file, "w") do |file|
306
+ file.write(JSON.pretty_generate(config_json))
307
+ file.close
308
+ end
309
+
310
+ git_addpush_repo(path: config_repo_dir, message: "update app shared secret", commit_file_params: ["config.json"])
311
+
312
+ end
313
+
314
+
315
+ end
316
+
317
+ def create_appstore_iap(appstore_iap_file: nil)
318
+
319
+ temp_iap_json = JSON.parse(File.read(appstore_iap_file))
320
+
321
+ if @check_flag
322
+ check_purchase_items_right(in_app_purchase_json: temp_iap_json, config_json: @config_json)
323
+ end
324
+
325
+ in_app_purchase_json = process_purchase_items(in_app_purchase_json: temp_iap_json, iap_tester: @iaptester)
326
+
327
+
328
+ app_list_response = @app_store_connect.list_apps(filter: {
329
+ bundle_id: @bundle_id
330
+ })
331
+
332
+
333
+ # puts app_list_response
334
+ current_app = nil
335
+
336
+ app_list_response[:data].each do |app_info|
337
+ if app_info[:attributes][:bundle_id].to_s.eql?(@bundle_id)
338
+ current_app = app_info
339
+ end
340
+ end
341
+
342
+ app_id = current_app[:id]
343
+
344
+ puts " "
345
+ puts "app_id : #{app_id} +++++++++"
346
+
347
+ puts " "
348
+ puts "创建消耗型购买项============="
349
+ create_consumable_iap_items(appstore_client: @app_store_connect, app_id: app_id, in_app_purchase_items: in_app_purchase_json["consumable"])
350
+
351
+ puts " "
352
+ puts "创建非消耗型购买项============="
353
+ create_consumable_iap_items(appstore_client: @app_store_connect, app_id: app_id, in_app_purchase_items: in_app_purchase_json["non_consumable"])
354
+
355
+ puts " "
356
+ puts "创建非续期订阅购买项============="
357
+ create_consumable_iap_items(appstore_client: @app_store_connect, app_id: app_id, in_app_purchase_items: in_app_purchase_json["non_renew"])
358
+
359
+ puts " "
360
+ puts "创建自动订阅购买项============="
361
+ create_subscriptions_groups(appstore_client: @app_store_connect, app_id: app_id, subscription_group_items: in_app_purchase_json["auto_renew"])
362
+
363
+ update_app_share_secrets(app_id: app_id)
364
+ end
365
+
366
+ def login_api_key(apple_id: nil)
367
+
368
+ load_apikey_config = false
369
+ api_key_json = nil
370
+ pindo_dir = File.expand_path(pindo_single_config.pindo_dir)
371
+ api_key_file = File.join(pindo_dir, "api_key.json")
372
+
373
+ if File.exist?(api_key_file)
374
+ api_key_json = JSON.parse(File.read(api_key_file))
375
+ if !api_key_json.nil? && !api_key_json[apple_id].nil? && !api_key_json[apple_id]["issuer_id"].nil?
376
+ issuer_id = api_key_json[apple_id]["issuer_id"]
377
+ key_id = api_key_json[apple_id]["key_id"]
378
+ private_key = api_key_json[apple_id]["private_key"]
379
+ load_apikey_config = true
380
+ end
381
+ end
382
+
383
+ if !load_apikey_config
384
+ issuer_id = ask('请输入 issuer_id: ')
385
+ key_id = ask('请输入 key_id: ')
386
+ private_key_file = ask('请输入private key的文件路径: ')
387
+ private_key = File.read(File.new(private_key_file))
388
+
389
+ api_key_json = api_key_json || {}
390
+ api_key_json[apple_id] = api_key_json[apple_id] || {}
391
+ api_key_json[apple_id]["issuer_id"] = issuer_id
392
+ api_key_json[apple_id]["key_id"] = key_id
393
+ api_key_json[apple_id]["private_key"] = private_key
394
+
395
+ answer = agree("请确认上面信息是否正确(Y/n):")
396
+ unless answer
397
+ raise Informative, "重新输入 !!!"
398
+ end
399
+
400
+ File.open(api_key_file, "w") do |f|
401
+ f.write(JSON.pretty_generate(api_key_json))
402
+ end
403
+ load_apikey_config = true
404
+
405
+
406
+ end
407
+
408
+ if load_apikey_config
409
+
410
+ puts "issuer_id : #{issuer_id}"
411
+ puts "key_id : #{key_id}"
412
+ puts "private_key : #{private_key}"
413
+
414
+ AppStoreDevApi.config = {
415
+ issuer_id: issuer_id,
416
+ key_id: key_id,
417
+ private_key: private_key
418
+ }
419
+ @app_store_connect = AppStoreDevApi::Client.new
420
+ end
421
+ end
422
+
423
+
424
+ def check_purchase_items_right(in_app_purchase_json: nil, config_json: nil)
425
+
426
+ puts
427
+ puts "检查iap 配置"
428
+ puts
429
+ app_version = @config_json["app_info"]["app_version"]
430
+ bundle_id = @bundle_id
431
+ platform = @config_json["app_setting"]["kGUKeyAppPlatform"] || @config_json["app_setting"]["app_platform"]
432
+ app_type = @config_json["project_info"]["app_type"]
433
+
434
+ boss_client = BossClient.new(app_type: app_type, is_dev: false)
435
+ data_json = Pindo::Command::Utils::Boss::get_config(boss_client: boss_client, app_version: app_version, bundle_id: bundle_id, platform: platform)
436
+
437
+ gate_vip_items = get_gate_vip_items(app_type: app_type, boss_config: data_json["data"])
438
+
439
+ if !gate_vip_items.nil? && gate_vip_items.size > 0
440
+ itc_vip_items = []
441
+ in_app_purchase_json["auto_renew"].each do |group_item|
442
+ items = group_item["subscription_items"] || []
443
+ itc_vip_items += items
444
+ items = group_item["items"] || []
445
+ itc_vip_items += items
446
+ end
447
+
448
+
449
+ gate_vip_items.each do |offer_id, boss_iap_item|
450
+ boss_product_id = boss_iap_item["iap"]["product_id"]
451
+ boss_price = boss_iap_item["iap"]["price"]
452
+ free_days = boss_iap_item["free_days"]
453
+
454
+ itc_vip_item = itc_vip_items.find { |item| item["product_id"].eql?(boss_product_id)}
455
+ if !itc_vip_item.nil?
456
+ puts "product id: #{boss_product_id} 与boss一致!"
457
+
458
+ if boss_price.eql?("$"+itc_vip_item["price"].to_s)
459
+ puts " 价格一致! 价格:#{boss_price}"
460
+ else
461
+ local_price = itc_vip_item["price"].to_s
462
+ raise Informative, " 价格不一致 Boss:#{boss_price}, Local:$#{local_price}"
463
+ end
464
+
465
+ if !itc_vip_item["subscription_free_trial"].nil? && !free_days.nil? && itc_vip_item["subscription_free_trial"].to_s.eql?(free_days.to_s + "d")
466
+ puts " 有三天试用, 一致!"
467
+ elsif itc_vip_item["subscription_free_trial"].nil? && free_days.nil?
468
+ puts " 无三天试用, 一致!"
469
+ else
470
+ raise Informative, " 三天试用不一致!"
471
+ end
472
+
473
+
474
+ else
475
+ raise Informative, "product id: #{boss_product_id} 与boss不匹配!"
476
+ end
477
+ end
478
+ end
479
+
480
+ offer_vip_items = get_gate_vip_items(app_type: app_type, boss_config: data_json["data"])
481
+ end
482
+
483
+
484
+
485
+
486
+ def get_gate_vip_items(app_type: nil, boss_config: nil)
487
+ vip_items = nil
488
+
489
+ offer_ids = []
490
+ if !boss_config.nil? && !boss_config["content"].nil? && !boss_config["content"]["client"].nil? && !boss_config["content"]["client"]["vip"].nil? && !boss_config["content"]["client"]["vip"]["style_list"].nil?
491
+ style_list = boss_config["content"]["client"]["vip"]["style_list"] || {}
492
+ style_item = style_list.find { |item| item["style"].eql?("tribble_style")}
493
+ offer_ids = style_item["offer"] || []
494
+ end
495
+
496
+ if !boss_config.nil? && !boss_config["content"].nil? && !boss_config["content"]["common"].nil? && !boss_config["content"]["common"].nil? && !boss_config["content"]["common"]["offer_map"].nil?
497
+ boss_vip_items = boss_config["content"]["common"]["offer_map"]
498
+ vip_items = vip_items || {}
499
+ offer_ids.each do |offer_id|
500
+ if !boss_vip_items[offer_id].nil?
501
+ vip_items[offer_id] = boss_vip_items[offer_id]
502
+ end
503
+ end
504
+ end
505
+
506
+ return vip_items
507
+ end
508
+
509
+ def process_purchase_items(in_app_purchase_json: nil, iap_tester: nil)
510
+ new_in_app_purchase_json = {}
511
+ new_in_app_purchase_json["iaptester"] = @iaptester
512
+ new_in_app_purchase_json["bundle_id"] = @bundle_id
513
+
514
+ new_in_app_purchase_json["review_note"] = "username: " + iap_tester["email"] + " password: " + iap_tester["password"]
515
+
516
+ puts
517
+ puts
518
+ puts new_in_app_purchase_json["review_note"]
519
+
520
+ in_app_purchase_type = "CONSUMABLE"
521
+ items = process_consumable_iap_items(in_app_purchase_type: in_app_purchase_type, in_app_purchase_items: in_app_purchase_json["consumable"], iap_tester: iap_tester)
522
+ new_in_app_purchase_json["consumable"] = items
523
+
524
+ in_app_purchase_type = "NON_CONSUMABLE"
525
+ items = process_consumable_iap_items(in_app_purchase_type: in_app_purchase_type, in_app_purchase_items: in_app_purchase_json["non_consumable"], iap_tester: iap_tester)
526
+ new_in_app_purchase_json["non_consumable"] = items
527
+
528
+ in_app_purchase_type = "NON_RENEWING_SUBSCRIPTION"
529
+ items = process_consumable_iap_items(in_app_purchase_type: in_app_purchase_type, in_app_purchase_items: in_app_purchase_json["non_renew"], iap_tester: iap_tester)
530
+ new_in_app_purchase_json["non_renew"] = items
531
+
532
+ in_app_purchase_type = "Subscriptions"
533
+ new_subscrptions_json = process_subscriptions_json(subscription_json: in_app_purchase_json["auto_renew"], iap_tester: iap_tester)
534
+ new_in_app_purchase_json["auto_renew"] = new_subscrptions_json
535
+
536
+ File.open(File.join(@iapconfig_dir, "iap_log.json"), "w") do |f|
537
+ f.write(JSON.pretty_generate(new_in_app_purchase_json))
538
+ end
539
+
540
+ return new_in_app_purchase_json;
541
+
542
+ end
543
+
544
+ def process_consumable_iap_items(in_app_purchase_type: nil, in_app_purchase_items: nil, iap_tester: nil)
545
+
546
+ tier_json = get_price_tier_dict
547
+ iap_consumable_tier_dict = tier_json["consumable_price_tier"]
548
+
549
+ in_app_purchase_items = in_app_purchase_items || []
550
+ new_in_app_purchase_items = []
551
+ in_app_purchase_items.each do |purchase_item|
552
+ new_iap_items = {}
553
+ new_iap_items["reference_name"] = purchase_item["reference_name"].to_s.strip || purchase_item["product_name"].to_s.strip
554
+ if !purchase_item["reference_name"].nil? && purchase_item["reference_name"].length > 64
555
+ puts purchase_item["reference_name"]
556
+ puts "length: #{purchase_item["reference_name"].size}"
557
+ raise Informative, "Reference Name 超出限定的64个字符 !!!"
558
+ end
559
+
560
+ temp_product_id = purchase_item["product_id"].to_s.strip
561
+ new_iap_items["product_id"] = temp_product_id
562
+ if new_iap_items["product_id"].include?("PRODUCT_BUNDLE_IDENTIFIER")
563
+ temp_product_id = new_iap_items["product_id"]
564
+ temp_product_id = temp_product_id.gsub(/PRODUCT_BUNDLE_IDENTIFIER/, @bundle_id);
565
+ new_iap_items["product_id"] = temp_product_id
566
+ end
567
+ if !new_iap_items["product_id"].nil? && new_iap_items["product_id"].length > 100
568
+ puts temp_product_id
569
+ raise Informative, "product_id 超出限定的100个字符 !!!"
570
+ end
571
+
572
+ new_iap_items["in_app_purchase_type"] = in_app_purchase_type
573
+ new_iap_items["price"] = purchase_item["price"].to_s.strip
574
+ new_iap_items["price_tier"] = iap_consumable_tier_dict[new_iap_items["price"].to_s]
575
+
576
+ if new_iap_items["price_tier"].nil?
577
+ raise "购买项价格错误 " + new_iap_items["product_id"] + " 价格 :" + purchase_item["price"]
578
+ end
579
+
580
+ new_iap_items["localization"] = {}
581
+ new_iap_items["localization"]["en-US"] = {}
582
+ new_iap_items["localization"]["en-US"]["locale"] = "en-US"
583
+ new_iap_items["localization"]["en-US"]["display_name"] = purchase_item["display_name"].to_s.strip
584
+
585
+ if !new_iap_items["localization"]["en-US"]["display_name"].nil? && new_iap_items["localization"]["en-US"]["display_name"].length > 30
586
+ puts temp_product_id
587
+ raise Informative, "Display Name 超出限定的30个字符 !!!"
588
+ end
589
+
590
+ new_iap_items["localization"]["en-US"]["description"] = purchase_item["description"].to_s.strip
591
+ if !new_iap_items["localization"]["en-US"]["description"].nil? && new_iap_items["localization"]["en-US"]["description"].length > 45
592
+ puts temp_product_id
593
+ raise Informative, "purchuse description 超出限定的45个字符 !!!"
594
+ end
595
+
596
+ new_iap_items["review_note"] = "username: " + iap_tester["email"] + " password: " + iap_tester["password"]
597
+ if !purchase_item["review_screenshot"].nil?
598
+ new_iap_items["review_screenshot"] = File.basename(purchase_item["review_screenshot"])
599
+ new_iap_items["review_screenshot_fullpath"] = File.join(@iapconfig_dir, new_iap_items["review_screenshot"])
600
+ else
601
+ new_iap_items["review_screenshot_fullpath"] = nil
602
+ end
603
+
604
+ new_in_app_purchase_items << new_iap_items
605
+ end
606
+
607
+ return new_in_app_purchase_items
52
608
  end
53
609
 
610
+
611
+ def process_subscriptions_json(subscription_json: nil, iap_tester: nil)
612
+
613
+ tier_json = get_price_tier_dict
614
+ subscription_json = subscription_json || []
615
+ new_subscription_json = []
616
+ subscription_json.each do |subscription_group_item|
617
+ new_subscription_group_item = {}
618
+ new_subscription_group_item["group_reference_name"] = subscription_group_item["group_reference_name"].to_s.strip
619
+ new_subscription_group_item["localization"] = {}
620
+ new_subscription_group_item["localization"]["en-US"] = {}
621
+ new_subscription_group_item["localization"]["en-US"]["locale"] = "en-US"
622
+ new_subscription_group_item["localization"]["en-US"]["group_display_name"] = subscription_group_item["group_display_name"].to_s.strip
623
+
624
+ if !new_subscription_group_item["localization"]["en-US"]["group_display_name"].nil? && new_subscription_group_item["localization"]["en-US"]["group_display_name"].length > 45
625
+ puts new_subscription_group_item["localization"]["en-US"]["group_display_name"]
626
+ raise Informative, "group_display_name 超出限定的75个字符 !!!"
627
+ end
628
+ new_subscription_group_item["localization"]["en-US"]["custom_app_name"] = subscription_group_item["custom_app_name"].to_s.strip
629
+ if !new_subscription_group_item["localization"]["en-US"]["custom_app_name"].nil? && new_subscription_group_item["localization"]["en-US"]["custom_app_name"].length > 45
630
+ puts new_subscription_group_item["localization"]["en-US"]["custom_app_name"]
631
+ raise Informative, "custom_app_name 超出限定的30个字符 !!!"
632
+ end
633
+
634
+ new_subscription_group_item["subscription_items"] = []
635
+
636
+ old_subscription_group_items = subscription_group_item["subscription_items"] || []
637
+ if old_subscription_group_items.size <= 0 && !subscription_group_item["items"].nil?
638
+ old_subscription_group_items = subscription_group_item["items"] || []
639
+ end
640
+
641
+ new_subscription_group_item["subscription_items"] = process_subscriptions_item(subscription_items: old_subscription_group_items, iap_tester: iap_tester, subscription_duration_dict: tier_json["subscription_duration"])
642
+ new_subscription_json << new_subscription_group_item
643
+ end
644
+
645
+ return new_subscription_json
646
+ end
647
+
648
+ def process_subscriptions_item(subscription_items: nil, iap_tester: nil, subscription_duration_dict: nil)
649
+
650
+
651
+ subscription_items = subscription_items || []
652
+ new_subscription_items = []
653
+ subscription_items.each do |subs_item|
654
+ new_subs_item = {}
655
+
656
+ new_subs_item["reference_name"] = subs_item["reference_name"].to_s.strip || subs_item["product_name"].to_s.strip
657
+ if !new_subs_item["reference_name"].nil? && new_subs_item["reference_name"].length > 64
658
+ puts new_subs_item["reference_name"]
659
+ raise Informative, " Reference Name 超出限定的64个字符 !!!"
660
+ end
661
+
662
+ new_subs_item["product_id"] = subs_item["product_id"].to_s.strip
663
+ temp_product_id = new_subs_item["product_id"]
664
+ if new_subs_item["product_id"].include?("PRODUCT_BUNDLE_IDENTIFIER")
665
+ temp_product_id = new_subs_item["product_id"]
666
+ temp_product_id = temp_product_id.gsub(/PRODUCT_BUNDLE_IDENTIFIER/, @bundle_id);
667
+ new_subs_item["product_id"] = temp_product_id
668
+ end
669
+
670
+ if !new_subs_item["product_id"].nil? && new_subs_item["product_id"].length > 100
671
+ puts temp_product_id
672
+ raise Informative, "product_id 超出限定的100个字符 !!!"
673
+ end
674
+
675
+ new_subs_item["family_sharable"] = false
676
+ new_subs_item["price"] = subs_item["price"].to_s.strip
677
+ new_subs_item["subscription_duration"] = subscription_duration_dict[subs_item["subscription_duration"]]
678
+
679
+ if !subs_item["subscription_free_trial"].nil?
680
+ new_subs_item["subscription_free_trial"] = subs_item["subscription_free_trial"]
681
+ end
682
+ new_subs_item["localization"] = {}
683
+ new_subs_item["localization"]["en-US"] = {}
684
+ new_subs_item["localization"]["en-US"]["locale"] = "en-US"
685
+ new_subs_item["localization"]["en-US"]["display_name"] = subs_item["display_name"].to_s.strip
686
+ if !new_subs_item["localization"]["en-US"]["display_name"].nil? && new_subs_item["localization"]["en-US"]["display_name"].length > 30
687
+ puts temp_product_id
688
+ raise Informative, "Display Name 超出限定的30个字符 !!!"
689
+
690
+ end
691
+ new_subs_item["localization"]["en-US"]["description"] = subs_item["description"].to_s.strip
692
+ if !new_subs_item["localization"]["en-US"]["description"].nil? && new_subs_item["localization"]["en-US"]["description"].length > 45
693
+ puts temp_product_id
694
+ raise Informative, "Description 超出限定的45个字符 !!!"
695
+ end
696
+
697
+ new_subs_item["review_note"] = "username: " + iap_tester["email"] + " password: " + iap_tester["password"]
698
+ if !subs_item["review_screenshot"].nil?
699
+ new_subs_item["review_screenshot"] = File.basename(subs_item["review_screenshot"])
700
+ new_subs_item["review_screenshot_fullpath"] = File.join(@iapconfig_dir, new_subs_item["review_screenshot"])
701
+ else
702
+ new_subs_item["review_screenshot_fullpath"] = nil
703
+ end
704
+
705
+
706
+ new_subscription_items << new_subs_item
707
+ end
708
+
709
+ return new_subscription_items
710
+ end
711
+
712
+
713
+ def itc_has_sandbox_tester(email: nil)
714
+
715
+ has_sandbox_account = false
716
+ begin
717
+ response = @app_store_connect.list_sandbox_testers()
718
+ # puts JSON.pretty_generate(response)
719
+ has_sandbox_account = false
720
+ response[:data].each do |tester_item|
721
+ if tester_item[:attributes][:ac_account_name].to_s.eql?(@iaptester['email'])
722
+ has_sandbox_account = true
723
+ # puts "itc_has_sandbox_tester SandboxTester : " + @iaptester['email'] + "账号已经存在"
724
+ end
725
+ end
726
+ rescue => err
727
+
728
+ end
729
+
730
+ if !has_sandbox_account
731
+
732
+ begin
733
+ testers = Spaceship::ConnectAPI::SandboxTester.all()
734
+ if !testers.nil? && testers.size > 0
735
+ testers.each do |tester_item|
736
+ # puts tester_item.email
737
+ if tester_item.email.to_s.eql?(@iaptester['email'])
738
+ has_sandbox_account = true
739
+ # puts "itc_has_sandbox_tester ConnectAPI SandboxTester : " + @iaptester['email'] + "账号已经存在"
740
+ end
741
+ end
742
+ end
743
+ rescue => err
744
+
745
+ end
746
+
747
+ end
748
+
749
+ return has_sandbox_account
750
+ end
751
+
752
+
753
+
754
+ def create_iap_tester_ex()
755
+ b_create_success = false
756
+ testers = nil
757
+ time = Time.local(1995 - rand(10), 12 - rand(10), 25 - rand(20))
758
+ birth_date = time.strftime("%Y-%m-%d")
759
+ begin
760
+ testers = Spaceship::ConnectAPI::SandboxTester.create(
761
+ first_name: @iaptester['first_name'],
762
+ last_name: @iaptester['last_name'],
763
+ email: @iaptester['email'],
764
+ password: @iaptester['password'],
765
+ confirm_password: @iaptester['password'],
766
+ secret_question: @iaptester['password'] + '1234?',
767
+ secret_answer: @iaptester['password'],
768
+ birth_date: birth_date,
769
+ app_store_territory: @iaptester['territory'] || 'USA'
770
+ )
771
+ rescue => err
772
+ b_create_success = false
773
+ end
774
+
775
+ end
776
+
777
+
778
+ def create_iap_tester()
779
+
780
+ sandbox_tester_file = File.join(@iapconfig_dir, "iap_tester.json")
781
+ sandbox_tester_json = {}
782
+ if File.exist?(sandbox_tester_file)
783
+ sandbox_tester_json = JSON.parse(File.read(sandbox_tester_file))
784
+ else
785
+ raise Informative, "缺少沙盒配置文件iap_tester.json !!!"
786
+ end
787
+
788
+ @iaptester = nil
789
+ if !sandbox_tester_json.nil? && !sandbox_tester_json["iaptester"].nil?
790
+ @iaptester = sandbox_tester_json["iaptester"]
791
+ end
792
+
793
+ if !@iaptester.nil? && @iaptester["need_create"]
794
+ puts
795
+ puts
796
+ if itc_has_sandbox_tester(email: @iaptester["email"])
797
+
798
+ puts "已存在沙盒账号:#{@iaptester["email"]},无需创建!!!"
799
+ else
800
+ puts "没有沙盒账号:#{@iaptester["email"]},需新创建!!!"
801
+
802
+ create_iap_tester_ex()
803
+ if itc_has_sandbox_tester(email: @iaptester["email"])
804
+ puts "沙盒账号:#{@iaptester["email"]},创建成功!!!"
805
+ else
806
+ raise Informative, "沙盒账号:#{@iaptester["email"]},创建失败!!!"
807
+ end
808
+ end
809
+ else
810
+ raise Informative, "没有Sandbox Tester !!!"
811
+ end
812
+
813
+ end
814
+
815
+
816
+
817
+
54
818
  end
55
819
  end
56
820
  end