Class: Paypkg

Inherits:
Object
  • Object
show all
Includes:
JSON, Version
Defined in:
lib/paypkg.rb,
lib/paypkg.rb,
lib/paypkg/refund-sale.rb,
lib/paypkg/accept-pp-payment.rb,
lib/paypkg/store-credit-card.rb,
lib/paypkg/delete-credit-card.rb,
lib/paypkg/retrieve-credit-card.rb,
lib/paypkg/validate-credit-card.rb,
lib/paypkg/accept-stored-cc-payment.rb,
lib/paypkg/retrieve-payment-resource.rb,
lib/paypkg/retrieve-sale-transaction.rb,
lib/paypkg/accept-tendered-cc-payment.rb,
lib/paypkg/retrieve-refund-transaction.rb

Overview

Retrieve Refund Transaction ###

Constant Summary

Constants included from Version

Version::MODIFIED, Version::VERSION

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#hashObject (readonly)

Returns the value of attribute hash.



45
46
47
# File 'lib/paypkg.rb', line 45

def hash
  @hash
end

#jsonObject (readonly)

Returns the value of attribute json.



45
46
47
# File 'lib/paypkg.rb', line 45

def json
  @json
end

Returns the value of attribute link.



45
46
47
# File 'lib/paypkg.rb', line 45

def link
  @link
end

#modeObject (readonly)

Returns the value of attribute mode.



45
46
47
# File 'lib/paypkg.rb', line 45

def mode
  @mode
end

#requestObject (readonly)

Returns the value of attribute request.



45
46
47
# File 'lib/paypkg.rb', line 45

def request
  @request
end

#statusObject (readonly)

Returns the value of attribute status.



45
46
47
# File 'lib/paypkg.rb', line 45

def status
  @status
end

Instance Method Details

#accept_pp_payment(amount, desc, approved_url, cancelled_url) ⇒ Object

This method is intended for use when you want to charge a user’s PayPay account. The user will be required to give an on-line approval.

The two urls shown here must be in your controller, and in your config/routes.rb file. See the execute_payment method below. You should store the payment_id you get back from this call (in session here).

Parameters:

  • amount
  • desc
  • approved_url
  • cancelled_url


19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/paypkg/accept-pp-payment.rb', line 19

def accept_pp_payment(amount, desc, approved_url, cancelled_url)
  set_access_token # we need this here to set the @website
  formatted_amount = "%0.2f"%amount
  json, status = call_paypal("/v1/payments/payment", "{
    'intent':'sale',
    'redirect_urls':{
      'return_url':'#{@website}/#{approved_url}/',
      'cancel_url':'#{@website}/#{cancelled_url}/'
    },
    'payer':{
      'payment_method':'paypal'
    },
    'transactions':[
      {
        'amount':{
          'total':'#{formatted_amount}',
          'currency':'USD'
        },
        'description':'#{desc}'
      }
    ]
  }")
  if (@status.last=='201') && (@hash.last[:state]=='created')
    @session[:payment_id] = @hash.last[:id]
    @link = @hash.last[:links].select{|link| link[:rel]=='approval_url'}[0][:href]
    return true
  else
    return false
  end
end

#accept_stored_cc_payment(card_id, amount, desc, email, payer_id) ⇒ Object

This method is intended for use when you want to charge a card stored in the vault.

Parameters:

  • card_id (String)

    Required.

  • amount (Numeric)

    Required.

  • desc (String)

    Required.

  • email (String)

    Required.

  • payer_id (String)

    Required.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/paypkg/accept-stored-cc-payment.rb', line 13

def accept_stored_cc_payment(card_id, amount, desc, email, payer_id)
  formatted_amount = "%0.2f"%amount
  call_paypal("/v1/payments/payment", "{
    'intent':'sale',
    'payer':{
      'payment_method':'credit_card',
      'payer_info':{
        'email':'#{email}'
      },
      'funding_instruments':[
        {
          'credit_card_token':{
            'credit_card_id':'#{card_id}',
            'payer_id':'#{payer_id}'
          }
        }
      ]
    },
    'transactions':[
      {
        'amount':{
          'total':'#{formatted_amount}',
          'currency':'USD'
        },
        'description':'#{desc}'
      }
    ]
  }")
  return (@status.last=='201') && (@hash.last[:state]=='approved')
end

#accept_tendered_cc_payment(type, number, expire_month, expire_year, cvv2, first_name, last_name, amount, desc) ⇒ Object

This call is intended for use when the card is presented, but not stored in the vault.

Parameters:

  • number (String)

    Required.

  • cvv2 (String)

    Optional.

  • first_name (String)

    Optional.

  • last_name (String)

    Optional.

  • expire_month (String)

    Required.

  • expire_year (String)

    Required.

  • amount (String)

    Required.

  • desc (String)

    Required.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/paypkg/accept-tendered-cc-payment.rb', line 16

def accept_tendered_cc_payment(type, number, expire_month, expire_year, cvv2, first_name,
  last_name, amount, desc)
  formatted_amount = "%0.2f"%amount
  json = "{
    'intent':'sale',
    'payer':{
      'payment_method':'credit_card',
      'funding_instruments':[
        {
          'credit_card':{
            'type':'#{type}',
            'number':'#{number}',"
  json << " 'cvv2':'#{cvv2}'," if cvv2
  json << " 'first_name':'#{first_name}'," if first_name
  json << " 'last_name':'#{last_name}'," if last_name
  json << " 'expire_month':'#{expire_month}',
            'expire_year':'#{expire_year}'
          }
        }
      ]
    },
    'transactions':[
      {
        'amount':{
          'total':'#{formatted_amount}',
          'currency':'USD'
        },
        'description':'#{desc}'
      }
    ]
  }"
  call_paypal("/v1/payments/payment", json)
  return (@status.last=='201') && (@hash.last[:state]=='approved')
end

#call_paypal(endpoint, data = nil, options = {reset: :yes, method: :get}) ⇒ json String, ...

The call_paypal method is used to call PayPal with a json string, and return the response json string and HTTP exit code – The json response, the response converted into a hash, and the status code are all stored – some calls (like validate_credit_card) actually make two PayPal calls, so the json, hash, and status are arrays

Examples:

call_paypal("/v1/payments/payment/PAY-2BG385817G530460DKNDSITI/execute/", "{
  'payer_id' : 'EC-71588296U3330482A'
}")

Parameters:

  • endpoint (String)

    The PayPal endpoint depends on which service you’re requesting – required

  • data (json String) (defaults to: nil)

    The “put” data, if any, in the form of a json string

  • reset (:yes or :no)

    If the json, hash, and status should be cleared, then :yes, or :no for the second PayPal call in a two call function

  • method (:get, :post, or :delete)

    The HTTP type

Returns:

  • (json String)

    The json response from PayPal, if any

  • (Hash)

    The json String converted into a hash

  • (String)

    The HTTP status code



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/paypkg.rb', line 121

def call_paypal(endpoint, data=nil, options={reset: :yes, method: :get})
  set_access_token
  options = {:reset => :yes, :method => :get}.merge(options)
  if options[:reset]==:yes
    @json = []
    @hash = []
    @status = []
    @request = []
  end
  case
    when options[:method]==:delete
      request = Net::HTTP::Delete.new(endpoint)
    when options[:method]==:post || data # a json string
      request = Net::HTTP::Post.new(endpoint)
    when options[:method]==:get
      request = Net::HTTP::Get.new(endpoint)
  end
  @request << data
  request.add_field("Content-Type","application/json")
  request.add_field("Authorization", "Bearer #{@access_token}")
  request.body = data.gsub("'",'"') if data
  response = @http.request(request)
  response.body = nil if response.body.empty?
  @json << response.body
  @hash << if response.body then JSON.parse(response.body, :symbolize_names=>true) else nil end
  @status << response.code
end

#delete_credit_card(vault_id) ⇒ Object

Delete a card from the vault. If Paypal already removed the card because it expired, just ignore the error. NO JSON STRING is returned with this call.



9
10
11
12
# File 'lib/paypkg/delete-credit-card.rb', line 9

def delete_credit_card(vault_id)
  call_paypal("/v1/vault/credit-card/#{vault_id}", nil, :method => :delete)
  return (@status.last=='204') && (@hash.last==nil)
end

#error_dataObject

This method collects the response data in such a way as to guarantee valid Ruby stuctures will be generated, i.e., you won’t get a runtime error because of nil, etc.

It produces 3 outputs:

(1) A non-nil error hash
 {
   :name => "VALIDATION_ERROR",
   :details => [
     {
       :field => "payer.funding_instruments[0].credit_card.number",
       :issue => "Value is invalid"
     },
     {
       :field => "payer.funding_instruments[0].credit_card.number",
       :issue => "Number is crap"
     }
   ],
   :message => "Invalid request - see details",
   :information_link => "https://developer.paypal.com/webapps/developer/docs/api/#VALIDATION_ERROR",
   :debug_id => "88dcf0c1730bb"
 }
(2) A non-nil response object
  <PaypkgResponse:0x00000001e60040 @name="VALIDATION_ERROR",
    @details=[
      <PaypkgResponse:0x00000001e73e10 @field="payer.funding_instruments[0].credit_card.number", @issue="Value is invalid">, \
      <PaypkgResponse:0x00000001e73988 @field="payer.funding_instruments[0].credit_card.number", @issue="Invalid card type"> \
    ],
   @message="Invalid request - see details", \
   @information_link="https://developer.paypal.com/webapps/developer/docs/api/#VALIDATION_ERROR", \
   @debug_id="88dcf0c1730bb"
  >
(3) A non-nil details array
  [
    {
      :field => "payer.funding_instruments[0].credit_card.number",
      :issue => "Value is invalid"
    },
    {
      :field => "payer.funding_instruments[0].credit_card.number",
      :issue => "Number is crap"
    }
  ]


199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/paypkg.rb', line 199

def error_data
    conditioned_details = []
    if @hash.last
      conditioned_hash = @hash.last
      # sometimes there's details, sometimes not
      if conditioned_hash.has_key?(:details)
        # process errors
        conditioned_hash[:details].each do |detail|
          conditioned_details << detail
        end
      end
    else
      status = @status.last
      case status[0]
      when '4'
        name = "CLIENT ERROR"
        message = "The request sent to PayPal was invalid"
      when '5'
        name = "SERVER ERROR"
        message = "PayPay's servers had a problem"
      else
        name = "UNKNOWN ERROR"
        message = ""
      end
      conditioned_hash = {
          :name => name,
          :message => "%s. PayPal returned status code %s"%[message,status],
          :information_link => "https://developer.paypal.com/webapps/developer/docs/api",
          :debug_id => "0000000000000"
        }
    end
    conditioned_response = PaypkgResponse.new(conditioned_hash)
    return conditioned_hash, conditioned_response, conditioned_details
end

#execute_payment(payer_id, payment_id) ⇒ Object

This is executed by the code at the ‘accepted’ url.

Your controller must provide two url’s that look like the code below, and are in your config/routes.rb

def approved
  pp = Paypkg.new(session)
  @ok = pp.execute_payment(params[:PayerID],session[:payment_id])
  ... more logic ...
end

def cancelled
  ... handle the user cancelling out ...
end

You should save the sale_id returned from this call in your database.

Parameters:

  • payer_id
  • payment_id


70
71
72
73
74
75
# File 'lib/paypkg/accept-pp-payment.rb', line 70

def execute_payment(payer_id, payment_id)
  call_paypal("/v1/payments/payment/#{payment_id}/execute/", "{
    'payer_id' : '#{payer_id}'
  }")
  return (@status.last=='200') && (@hash.last[:state]=='approved')
end

#refund_sale(sale_id, amount) ⇒ Object

You can request a partial or full refund of a previous sale. You CANNOT, however, refund more than the original sale. If you need to do that, you’ll have to do it manually through your PalPal account.

If you think you’ll ever want to look up the refund, you’ll have to save the refund_id in your database.

Parameters:

  • sale_id (String)

    Required.

  • amount (Numeric)

    Required.



16
17
18
19
20
21
22
23
24
25
# File 'lib/paypkg/refund-sale.rb', line 16

def refund_sale(sale_id, amount)
  formatted_amount = "%0.2f"%amount
  call_paypal("/v1/payments/sale/#{sale_id}/refund", "{
    'amount': {
      'total': #{formatted_amount},
      'currency': 'USD'
    }
  }")
  return (@status.last=='201') && (@hash.last[:state]=='completed')
end

#responsePaypkgResponse Instance

This is a getter which is used to obtain the PaypkgResponse Object from the hash

Returns:



152
153
154
# File 'lib/paypkg.rb', line 152

def response
  if hash.last then PaypkgResponse.new(hash.last) else nil end
end

#retrieve_credit_card(vault_id) ⇒ Hash

Retrieve a card from the vault.

Parameters:

  • vault_id (String)

    The vault_id PayPal assigned when you stored this card.

Returns:

  • (Hash)

    The card data, less the card number: only the last 4 digits of the card number are returned. You can get an error from this call if the card expired and PayPal automatically removed it from the vault. If that happens, you should delete it from your database.



14
15
16
17
# File 'lib/paypkg/retrieve-credit-card.rb', line 14

def retrieve_credit_card(vault_id)
  call_paypal("/v1/vault/credit-card/#{vault_id}")
  return (@status.last=='200') && (@hash.last[:state]=='ok')
end

#retrieve_payment_resource(payment_id) ⇒ json String

Use the payment_id to look up a payment.

Parameters:

  • payment_id (String)

    Required.

Returns:

  • (json String)

    The same json string returned when the payment was orginally authorized.



10
11
12
13
# File 'lib/paypkg/retrieve-payment-resource.rb', line 10

def retrieve_payment_resource(payment_id)
  call_paypal("/v1/payments/payment/#{payment_id}")
  return (@status.last=='200')
end

#retrieve_refund_transaction(refund_id) ⇒ json String

Use the refund_id to look up the refund.

Parameters:

  • refund_id (String)

    Required.

Returns:

  • (json String)

    The same json string retured when the refund was orginally completed.



10
11
12
13
# File 'lib/paypkg/retrieve-refund-transaction.rb', line 10

def retrieve_refund_transaction(refund_id)
  call_paypal("/v1/payments/refund/#{refund_id}")
  return (@status.last=='200') && (['pending', 'completed', 'refunded', 'partially_refunded'].index(@hash.last[:state])>0)
end

#retrieve_sale_transaction(sale_id) ⇒ json String

Use the sale_id to look up the sale.

Parameters:

  • sale_id (String)

    Required.

Returns:

  • (json String)

    The same json string retured when the sale was orginally completed.



10
11
12
13
# File 'lib/paypkg/retrieve-sale-transaction.rb', line 10

def retrieve_sale_transaction(sale_id)
  call_paypal("/v1/payments/sale/#{sale_id}")
  return (@status.last=='200') && (['pending', 'completed', 'refunded', 'partially_refunded'].index(@hash.last[:state])>0)
end

#store_credit_card(type, number, expire_month, expire_year, cvv2, first_name, last_name, line1, line2, city, state, postal_code, country_code, payer_id) ⇒ String

Store a credit card on the PalPal servers (in the vault, as the refer to it). A card so stored can be used later to make charges, as long as the expire date has not elapsed. Once the expire date elapses, eventually PayPal will automatically remove the card. The card vault_id will be returned from this call: you must save it in your database.

Parameters:

  • type (String)

    Required.

  • number (String)

    Required.

  • expire_month (Numeric)

    Required.

  • expire_year (Numeric)

    Required.

  • cvv2 (String)

    Required.

  • first_name (String)

    Required.

  • last_name (String)

    Required.

  • line1 (String)

    Required.

  • line2 (String)

    Optional. To omit this parameter, use nil.

  • city (String)

    Required.

  • state (String)

    Required.

  • postal_code (String)

    Required.

  • country_code (String)

    Required.

Returns:

  • (String)

    The vault_id: you must save this in your database!



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/paypkg/store-credit-card.rb', line 25

def store_credit_card(type, number, expire_month, expire_year, cvv2, first_name,
  last_name, line1, line2, city, state, postal_code, country_code, payer_id)
  json = "{
    'payer_id':'#{payer_id}',
    'type':'#{type}',
    'number':'#{number}',
    'expire_month':'#{expire_month}',
    'expire_year':'#{expire_year}',
    'cvv2':'#{cvv2}',
    'first_name':'#{first_name}',
    'last_name':'#{last_name}',
    'billing_address':{
      'line1':'#{line1}',\n"
  json << " 'line2':'#{line2}',\n" if line2
  json << " 'city':'#{city}',
      'state':'#{state}',
      'postal_code':'#{postal_code}',
      'country_code':'#{country_code}'
    }
  }"
  call_paypal("/v1/vault/credit-card", json)
  return (@status.last=='201') && (@hash.last[:state]=='ok')
end

#validate_credit_card(type, number, expire_month, expire_year, cvv2, first_name, last_name, line1, city, state, postal_code, country_code) ⇒ Object

Validate a credit card by charging $0.01, then voiding the authorization. The reason for doing this is to validate that an actual charge can be made to the card. If you just attempt to store the card, and you get a valid response, it just means that the card data had no syntactical errors, not that you could ACTUALLY charge the card.

Parameters:

  • type (String)

    Required.

  • number (String)

    Required.

  • expire_month (Numeric)

    Required.

  • expire_year (Numeric)

    Required.

  • cvv2 (String)

    Required.

  • first_name (String)

    Required.

  • last_name (String)

    Required.

  • line1 (String)

    Required if any other address fields are present.

  • city (String)

    Required if any other address fields are present.

  • state (String)

    Required if any other address fields are present.

  • postal_code (String)

    Required if any other address fields are present.

  • country_code (String)

    Required if any other address fields are present.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/paypkg/validate-credit-card.rb', line 23

def validate_credit_card(type, number, expire_month, expire_year, cvv2, \
  first_name, last_name, line1, city, state, postal_code, country_code)
  data = ""
  data << "{
    'intent':'authorize',
    'payer':{
      'payment_method':'credit_card',
      'funding_instruments':[
        {
          'credit_card':{
            'number':'#{number}',
            'type':'#{type}',
            'expire_month':#{expire_month},
            'expire_year':#{expire_year},
            'cvv2':'#{cvv2}',
            'first_name':'#{first_name}',
            'last_name':'#{last_name}',"
            if (!line1.empty?) || (!city.empty?) || (!state.empty?) || (!postal_code.empty?) || (!country_code.empty?)
            data << "              'billing_address':{
              'line1':'#{line1}',
              'city':'#{city}',
              'state':'#{state}',
              'postal_code':'#{postal_code}',
              'country_code':'#{country_code}'
            }"
            end
        data << "            }
        }
      ]
    },
    'transactions':[
      {
        'amount':{
          'total':0.01,
          'currency':'USD'
        },
        'description':'This is a validation transaction.'
      }
    ]
  }"
  call_paypal("/v1/payments/payment", data)
  if (@status.last=='201') && (@hash.last[:state]=='approved')
    link = @hash.last[:transactions][0][:related_resources][0][:authorization][:links].select{|link| link[:rel]=='void'}
    endpoint = link[0][:href][@uri_base.length..-1] # remove the uri_base from the beginning of the link
    call_paypal(endpoint, nil, :method => :post, :reset => :no)
    return true if (@status.last=='200') && (@hash.last[:state]=='voided')
  end
  return false
end