Skip to content

Instantly share code, notes, and snippets.

@kenchan
Created September 21, 2014 04:46
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kenchan/d90dafa88e8a6938c129 to your computer and use it in GitHub Desktop.
Save kenchan/d90dafa88e8a6938c129 to your computer and use it in GitHub Desktop.

ActiveMerchant読み読み

3D Secure

issue を見るとなんかありそう

何?

  • invoice
  • payment
  • address
  • customer_data

新しいGatewayの作り方

Generatorを実行する

test/fixtures.yml

credentialが追加されてる

lib/active_merchant/billing/gateways/your_payment_service.rb

サンプル込のやつができてる。テンプレートは generators/gateway/templates/gateway.rb

      self.test_url = 'https://beta.test.jp/cgi-bin/order'
      self.live_url = 'https://example.com/live'

      self.supported_countries = ['JP']
      self.default_currency = 'JPY'
      # Webpayのを見て足したんだけど、あとで見る
      self.money_format = :cents
      self.supported_cardtypes = [:visa, :master, :american_express, :discover]

      # ここから下はどこで使われるだろう
      self.homepage_url = 'http://www.example.net/'
      self.display_name = 'New Gateway'
      def initialize(options={})
        requires!(options, :contact_code)
        super
      end

lib/active_merchant/billing/gateway.rb|222| def requires!(hash, *params)

        params.each do |param|
          if param.is_a?(Array)
            raise ArgumentError.new("Missing required parameter: #{param.first}") unless hash.has_key?(param.first)

            valid_options = param[1..-1] # 先頭以外
            raise ArgumentError.new("Parameter: #{param.first} must be one of #{valid_options.to_sentence(:words_connector => 'or')}") unless valid_options.include?(hash[param.first])
          else
            raise ArgumentError.new("Missing required parameter: #{param}") unless hash.has_key?(param)
          end
        end

arrayのarrayも受け付けている?

> requires!({contact_code: 4}, [:contact_code, 1, 2, 3]) # => ArgumentError

@optionsしているだけ

      def initialize(options = {})
        @options = options
      end

publicなインタフェースとかオプションとか

lib/active_merchant/billing/gateway.rb のrdoc

    # * <tt>purchase(money, credit_card, options = {})</tt>
    # * <tt>authorize(money, credit_card, options = {})</tt>
    # * <tt>capture(money, authorization, options = {})</tt>
    # * <tt>void(identification, options = {})</tt>
    # * <tt>refund(money, identification, options = {})</tt>
    # * <tt>verify(credit_card, options = {})</tt>
    #
    # Some gateways also support features for storing credit cards:
    #
    # * <tt>store(credit_card, options = {})</tt>
    # * <tt>unstore(identification, options = {})</tt>

これ以外もわりと自由に定義しているのも結構ある。

purchase

実際の購入処理。

第二引数は payment_method になっているのも沢山ある

      def purchase(...)
        post = {}
        add_invoice(post, money, options)
        add_payment(post, payment)
        add_address(post, payment, options)
        add_customer_data(post, options)

        commit('sale', post)

add_xxx みたいなのは全部テンプレートの中にある。

      def add_invoice(post, options)
        post[:invoiceReference] = options[:order_id] || options[:invoice]
        post[:invoiceDescription] = options[:description]
      end

ようはハッシュに詰めてcommit。

      def commit(action, parameters)
        url = (test? ? test_url : live_url)
        response = parse(ssl_post(url, post_data(action, parameters)))

        Response.new(
          success_from(response),
          message_from(response),
          response,
          authorization: authorization_from(response),
          test: test?
        )
      end

ssl_postにはURLと何かを渡す。

include PostsData shopify/active_utils にあった。

superclass_delegating_accessor ってなんだ?ActiveSupport! どこに差し込まれるんだろう (core_ext/class/deligating_attributes)

ActiveMerchant から切り出しただけだったけど、ActiveUtilsにちゃんとやるわーのPR Shopify/active_utils#36

ssl_post

    def ssl_get(endpoint, headers={})
      ssl_request(:get, endpoint, nil, headers)
    end

    def ssl_post(endpoint, data, headers = {})
      ssl_request(:post, endpoint, data, headers)
    end

    def ssl_request(method, endpoint, data, headers)
      handle_response(raw_ssl_request(method, endpoint, data, headers))
    end

    def raw_ssl_request(method, endpoint, data, headers = {})
      logger.warn "#{self.class} using ssl_strict=false, which is insecure" if logger unless ssl_strict
      ...
      connection.request(method, data, headers)
    end

    private

    def handle_response(response)
      case response.code.to_i
      when 200...300
        response.body
      else
        raise ResponseError.new(response)
      end
    end

最終的には connection.requrest にどう渡すか。SSL通信がめんどくさい?

Connection、Benchmarkで実行時間をとって、infoログにだしてる!

ssl_postの第二引数にはHTTP bodyをあげる。もしendpointが変わるようなら、urlの方を変えないといけない。

parse には何が来る? Net::HTTP#postの返り値。handle_responseで200番台以外は例外になっている

Net::HTTP::Response#body みたいなやつがきそう。(handle_responseの中)

        Response.new(
          success_from(response),
          message_from(response),
          response,
          authorization: authorization_from(response),
          test: test?
        )

lib/active_merchant/billing/response.rb

    class Response
      attr_reader :params, :message, :test, :authorization, :avs_result, :cvv_result

      def success?
        @success
      end

      def test?
        @test
      end

      def fraud_review?
        @fraud_review
      end

      def initialize(success, message, params = {}, options = {})
        @success, @message, @params = success, message, params.stringify_keys
        @test = options[:test] || false
        @authorization = options[:authorization]
        @fraud_review = options[:fraud_review]

        @avs_result = if options[:avs_result].kind_of?(AVSResult)
          options[:avs_result].to_hash
        else
          AVSResult.new(options[:avs_result]).to_hash
        end

        @cvv_result = if options[:cvv_result].kind_of?(CVVResult)
          options[:cvv_result].to_hash
        else
          CVVResult.new(options[:cvv_result]).to_hash
        end
      end
    end

authorization, fraund_review, AVSResultとCVVResultとは?

authorize

この金額を引き落とせますかー?の確認。

capture

authorizeに対して実際に引き落とす処理。

purchase = authorize + capture

https://github.com/Shopify/active_merchant/wiki/GatewaysVsIntegrations に説明がある。

void

トランザクションを無効にする(返金との違いは?)

refund (credit)

返金

verify

クレジットカードの有効性だけを確認する。

少額のauthorizeをしてからすぐvoidしている。

store

クレジットカード登録

unstore

クレジットカードの登録解除

複数の処理をまとめてやりたいとき

verifyの中などではよく使われている。

        MultiResponse.run(:use_first_response) do |r|
          r.process { authorize(100, credit_card, options) }
          r.process(:ignore_result) { void(r.authorization, options) }
        end
    class MultiResponse < Response
      def self.run(use_first_response = false, &block)
        new(use_first_response).tap(&block)
      end

      def initialize(use_first_response = false)
        @responses = []
        @use_first_response = use_first_response
        @primary_response = nil
      end
    end

runの第一引数はbooleanなんだけど…

なるほど、nilとfalse以外はtrueを巧妙につかって表現していると理解した。

普通に使うと、

  • use_first_response => false
  • ignore_result => false

最後に成功したRequestがprimary_requestになる

エラーが突き抜けちゃうけどいいのかなぁ

CreditCardモデル

bogusっていうテスト用のブランドも用意しているよ

creadit_card_method というのにブランド毎のカード番号バリデーションが入っている。

creadit_card_formatting は年月を指定したフォーマットに変換する

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment