这是indexloc提供的服务,不要输入任何密码
Skip to content
This repository was archived by the owner on Feb 1, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ GIT
PATH
remote: .
specs:
ProMotion-iap (0.1.0)
ProMotion-iap (0.2.0)

GEM
remote: https://rubygems.org/
Expand Down
24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ProMotion-iap

[![Gem Version](https://badge.fury.io/rb/ProMotion-iap.svg)](http://badge.fury.io/rb/ProMotion-iap)
[![Build Status](https://travis-ci.org/clearsightstudio/ProMotion-iap.svg)](https://travis-ci.org/clearsightstudio/ProMotion-iap)
[![Build Status](https://travis-ci.org/clearsightstudio/ProMotion-iap.svg)](https://travis-ci.org/clearsightstudio/ProMotion-iap)

ProMotion-iap is in-app purchase notification support for the
popular RubyMotion gem [ProMotion](https://github.com/clearsightstudio/ProMotion).
Expand Down Expand Up @@ -42,7 +42,7 @@ class PurchaseScreen < PM::Screen
}
end

product.purchase do |status, transaction|
product.purchase do |status, data|
case status
when :in_progress
# Usually do nothing, maybe a spinner
Expand All @@ -54,13 +54,16 @@ class PurchaseScreen < PM::Screen
# They just canceled, no big deal.
when :error
# Failed to purchase
transaction.error.localizedDescription # => error message
data[:error].localizedDescription # => error message
end
end

product.restore do |status, product|
product.restore do |status, data|
if status == :restored
# Update your UI, notify the user
# data is a hash with :product_id, :error, and :transaction
else
# Tell the user it failed to restore
end
end

Expand Down Expand Up @@ -113,7 +116,7 @@ class PurchaseScreen < PM::Screen
}, {...}]
end

purchase_iap "productid" do |status, transaction|
purchase_iaps [ "productid" ], username: User.current.username do |status, transaction|
case status
when :in_progress
# Usually do nothing, maybe a spinner
Expand All @@ -129,9 +132,16 @@ class PurchaseScreen < PM::Screen
end
end

restore_iaps "productid" do |status, products|
restore_iaps [ "productid" ], username: User.current.username do |status, data|
if status == :restored
# Update your UI, notify the user
# This is called once for each product you're trying to restore.
data[:product_id] # => "productid"
elsif status == :error
# Update your UI to show that there was an error.
# This will only be called once, no matter how many products you are trying to restore.
# You'll get an NSError in your `data` hash.
data[:error].localizedDescription # => description of error
end
end

Expand All @@ -155,7 +165,7 @@ Prompts the user to login to their Apple ID and complete the purchase. The callb
#### restore_iaps(`*`product_ids, &callback)

Restores a previously purchased IAP to the user (for example if they have upgraded their device). This relies on the Apple ID the user
enters at the prompt. Unfortunately, if there is no purchase to restore for the signed-in account, no error message is generated and
enters at the prompt. Unfortunately, if there is no purchase to restore for the signed-in account, no error message is generated and
will fail silently.


Expand Down
52 changes: 44 additions & 8 deletions lib/ProMotion/iap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,32 @@ module ProMotion
module IAP
attr_accessor :completion_handlers

def purchase_iaps(*product_ids, &callback)
def purchase_iaps(product_ids, options={}, &callback)
iap_setup
retrieve_iaps product_ids do |products|
products.each do |product|
self.completion_handlers["purchase-#{product[:product_id]}"] = callback
payment = SKPayment.paymentWithProduct(product[:product])

payment = SKMutablePayment.paymentWithProduct(product[:product])
payment.applicationUsername = options[:username] if options[:username]

SKPaymentQueue.defaultQueue.addPayment(payment)
end
end
end
alias purchase_iap purchase_iaps

def restore_iaps(*product_ids, &callback)
def restore_iaps(product_ids, options={}, &callback)
iap_setup
retrieve_iaps product_ids do |products|
retrieve_iaps Array(product_ids) do |products|
products.each do |product|
self.completion_handlers["restore-#{product[:product_id]}"] = callback
end
self.completion_handlers["restore-all"] = callback # In case of error

if options[:username]
SKPaymentQueue.defaultQueue.restoreCompletedTransactionsWithApplicationUsername(options[:username])
else
SKPaymentQueue.defaultQueue.restoreCompletedTransactions
end
end
Expand Down Expand Up @@ -47,6 +56,7 @@ def iap_setup
end

def iap_shutdown
@completion_handlers = nil
SKPaymentQueue.defaultQueue.removeTransactionObserver(self)
end

Expand Down Expand Up @@ -79,18 +89,41 @@ def formatted_iap_price(price, price_locale)
end

def iap_callback(status, transaction, finish=false)
product_id = transaction.payment.productIdentifier
product_id = transaction_product_id(transaction)

if self.completion_handlers["purchase-#{product_id}"]
self.completion_handlers["purchase-#{product_id}"].call status, transaction
self.completion_handlers["purchase-#{product_id}"].call status, mapped_transaction(transaction)
self.completion_handlers["purchase-#{product_id}"] = nil if finish
end

if self.completion_handlers["restore-#{product_id}"]
self.completion_handlers["restore-#{product_id}"].call status, transaction
self.completion_handlers["restore-#{product_id}"].call status, mapped_transaction(transaction)
self.completion_handlers["restore-#{product_id}"] = nil if finish
end

SKPaymentQueue.defaultQueue.finishTransaction(transaction) if finish
end

def mapped_transaction(transaction)
if transaction.respond_to?(:payment)
{
product_id: transaction.payment.productIdentifier,
error: transaction.error,
transaction: transaction
}
else
{
product_id: nil,
error: transaction,
transaction: nil
}
end
end

def transaction_product_id(transaction)
transaction.respond_to?(:payment) ? transaction.payment.productIdentifier : "all"
end

public

# SKProductsRequestDelegate methods
Expand All @@ -99,7 +132,6 @@ def productsRequest(_, didReceiveResponse:response)
unless response.invalidProductIdentifiers.empty?
red = "\e[0;31m"
color_off = "\e[0m"
puts "#{red}PM::IAP Error - invalid product identifier(s) '#{response.invalidProductIdentifiers.join("', '")}' for application identifier #{NSBundle.mainBundle.infoDictionary['CFBundleIdentifier'].inspect}#{color_off}"
end
retrieved_iaps_handler(response.products, &self.completion_handlers["retrieve_iaps"]) if self.completion_handlers["retrieve_iaps"]
@products_request = nil
Expand Down Expand Up @@ -132,6 +164,10 @@ def paymentQueue(_, updatedTransactions:transactions)
end
end

def paymentQueue(_, restoreCompletedTransactionsFailedWithError:error)
iap_callback(:error, error)
end

end
end
::PM = ProMotion unless defined?(::PM)
21 changes: 18 additions & 3 deletions spec/purchase_iap_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,56 @@ def matchingIdentifier; end
successful_transaction = mock_transaction.new(SKPaymentTransactionStatePurchased, Struct.new(:code).new(nil), mock_payment.new("successfulproductid"))

it "returns success" do
called_callback = false
subject = TestIAP.new
subject.mock!(:completion_handlers, return: {
"purchase-successfulproductid" => ->(status, transaction) {
"purchase-successfulproductid" => ->(status, data) {
status.should === :purchased
data[:transaction].transactionState.should == SKPaymentTransactionStatePurchased
data[:error].code.should.be.nil
called_callback = true
},
})
subject.paymentQueue(nil, updatedTransactions:[ successful_transaction ])
called_callback.should.be.true
end
end

context "canceled transaction" do
canceled_transaction = mock_transaction.new(SKPaymentTransactionStateFailed, Struct.new(:code).new(SKErrorPaymentCancelled), mock_payment.new("canceledproductid"))

it "returns nil error" do
called_callback = false
subject = TestIAP.new
subject.mock!(:completion_handlers, return: {
"purchase-canceledproductid" => ->(status, transaction) {
"purchase-canceledproductid" => ->(status, data) {
status.should == :canceled
data[:transaction].should == canceled_transaction
data[:error].code.should == SKErrorPaymentCancelled
called_callback = true
},
})
subject.paymentQueue(nil, updatedTransactions:[ canceled_transaction ])
called_callback.should.be.true
end
end

context "invalid product" do
invalid_transaction = mock_transaction.new(SKPaymentTransactionStateFailed, Struct.new(:code).new(nil), mock_payment.new("invalidproductid"))

it "returns an error" do
called_callback = false
subject = TestIAP.new
subject.mock!(:completion_handlers, return: {
"purchase-invalidproductid" => ->(status, transaction) {
"purchase-invalidproductid" => ->(status, data) {
status.should == :error
data[:transaction].should == invalid_transaction
data[:error].code.should.be.nil
called_callback = true
},
})
subject.paymentQueue(nil, updatedTransactions:[ invalid_transaction ])
called_callback.should.be.true
end
end

Expand Down
28 changes: 27 additions & 1 deletion spec/restore_iaps_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,39 @@ def matchingIdentifier; end
restored_transaction = mock_transaction.new(SKPaymentTransactionStateRestored, Struct.new(:code).new(nil), mock_payment.new("restoredproductid"))

it "returns success" do
callback_called = false
subject = TestIAP.new
subject.mock!(:completion_handlers, return: {
"restore-restoredproductid" => ->(status, transaction) {
"restore-restoredproductid" => ->(status, data) {
status.should == :restored
data[:product_id].should == "restoredproductid"
data[:error].code.should.be.nil
data[:transaction].transactionState.should == SKPaymentTransactionStateRestored
callback_called = true
},
})
subject.paymentQueue(nil, updatedTransactions:[ restored_transaction ])
callback_called.should.be.true
end
end

context "error in restore" do
restored_transaction = mock_transaction.new(SKPaymentTransactionStateRestored, Struct.new(:code).new(nil), mock_payment.new("restoredproductid"))

it "returns success" do
callback_called = false
subject = TestIAP.new
subject.mock!(:completion_handlers, return: {
"restore-all" => ->(status, data) {
status.should == :error
data[:product_id].should.be.nil
data[:error].localizedDescription.should == "Failed to restore"
data[:transaction].should.be.nil
callback_called = true
},
})
subject.paymentQueue(nil, restoreCompletedTransactionsFailedWithError:Struct.new(:localizedDescription).new("Failed to restore"))
callback_called.should.be.true
end
end

Expand Down