Omise Schedule API 課金が失敗したときのフロー

Omiseの**Schedule API**は、仮に課金が失敗した場合、三回までリトライをしてくれます。

課金が失敗した場合、というのは、例えば課金対象のカードが使用限度額を超えているとか、カード自体が盗まれたカードとか....諸々の理由です。

この場合、[2回リトライ](https://www.omise.co/how-retries-work-for-scheduled-transactions)してダメだった場合、Scheduleは停止します。

この後の対処ですが、以下のような選択肢があると考えられます。

```
1. 該当の顧客のカード情報を更新して、課金を手動でやる
2. 該当の顧客のカード情報を更新して、スケジュールを作り直す 
3. 顧客へのサービスを停止する
```

## 該当の顧客カードの更新

課金が失敗したら、その顧客カードはダメダメですから、ダメダメじゃないカードに差し替えないといけません。

ダッシュボードを見て確認して、該当の顧客カード情報を更新する方法ももちろんあります。

また、SearchAPIというものを使って条件検索し、必要なものをとってくる方法もあります。

```
curl -s 'https://api.omise.co/search?scope=charge&filters\[failure_code\]=insufficient_fund' -u skey_moominmoomimoomiqijwxm04jmoomootit: | grep "cust_"
```

また、以下のようなスクリプトを使ったりもできます

```
#!/usr/bin/env ruby

require "json"

#
# the intension of this script is as follows:
#
#
# 1. collect all failed cards, using search API
# 2. charge those cards again
#

skey = ARGV[0]

if /^skey_/ !~ skey
  abort "SKEY #{skey} is invalid."
end


def get_failed_charges(skey)
  command = "curl -s 'https://api.omise.co/search" \
            "?scope=charge&filters\\"              \
            "[failure_code\\]=insufficient_fund' " \
            "-u #{skey}:"
  `#{command}`
end

def get_bad_cards(skey)
  bad_cards = JSON
                .parse(get_failed_charges(skey))["data"]
                .map   { |x|
                  location = x["card"]["location"]
                  [ 
                    /cust_[^\/]+/.match(location).to_s,
                    /card_[^\/]+/.match(location).to_s,
                    x["amount"],
                    x["created"],
                  ]
                }
                .select{ |x| x[0] != "" && x[1] != "" }
                .uniq
end                

result = ""

get_bad_cards(skey).each do |customer, old_card, amount, created|
  command =  "curl https://api.omise.co/charges \\\n " \
    " -X POST \\\n " \
    " -u #{skey}: \\\n " \
    " -d 'amount=#{amount}' \\\n " \
    " -d 'currency=jpy' \\\n " \
    " -d 'card=[NEW TOKEN OF CUSTOMER #{customer}]'"
   result = [
     "CREATED:                           #{created}",
     "OLD CARD WAS:                      #{old_card}",
     "YOU NEED TO GET TOKEN OF CUSTOMER: #{customer}\n\n",
     command,
   ].join("\n")
end    

def write_result_to_console(result)
  tmp = ".tmpfileforjson"
  File.open(tmp, "w") do |f|
    f.puts result
  end
  system("vim #{tmp}") 
end  

write_result_to_console(result)
```

そうするとvim編集モードでこんな感じで取れます:

```
CREATED:                           2018-03-03T01:21:24Z
OLD CARD WAS:                      card_test_5b4w0kuostyminyxwnk
YOU NEED TO GET TOKEN OF CUSTOMER: cust_test_5b4w0loxfeq7moorjo6


curl https://api.omise.co/charges \
  -X POST \
  -u skey_test_5b3moo04jminhtit: \
  -d 'amount=100' \
  -d 'currency=jpy' \
  -d 'card=[NEW TOKEN OF CUSTOMER cust_test_5b4w0loxfeq7qi5rjo6]'
```

そして、

```
YOU NEED TO GET TOKEN OF CUSTOMER: 
cust_test_5b4w0loxfeq7moorjo6
```

とあるので、この対象の顧客にOmise.jsを経由してカードの情報を新規に追加、更新してもらう必要があります。


```
$ omi customer get -v
```

```
$ curl -s https://api.omise.co/customers/cust_test_5b4vkgkeal49aafdo2k \
  -u skey_test_5moominmoominmoohtit:
```

そうすると、レスポンスオブジェクトの中に

```
  "cards": {
    "object": "list",
    "from": "1970-01-01T00:00:00Z",
    "to": "2018-03-04T12:04:50Z",
    "offset": 0,
    "limit": 20,
    "total": 1,
    "order": null,
    "location": "/customers/cust_test_5b4vkgkemoominfdo2k/cards",
    "data": [
      {
        "object": "card",
        "id": "card_test_5bmoomq3dmxe4qa3d3",
        "livemode": false,
        "location": "/customers/cust_test_5b4vmoominl49aafdo2k/cards/card_test_5b4vkmoominmoomin3d3",
        "country": "us",
        "city": "Bangkok",
        "postal_code": "10320",
        "financing": "",
        "bank": "JPMORGAN CHASE BANK, N.A.",
        "last_digits": "0011",
        "brand": "Visa",
        "expiration_month": 10,
        "expiration_year": 2018,
        "fingerprint": "ehfxeEmZu9ZOEmZJEh9VdmoominmooADRaITib2A=",
        "name": "Insufficient Fund",
        "security_code_check": true,
        "created": "2018-03-02T04:56:17Z"
      }
    ]

```
こんな風にかえってくるので、

```
card_test_5bmoomq3dmxe4qa3d3
```

これを使うんだな、となります。

## 手動で課金しなおす場合 

先ほどvim編集モードで見た奴の[NEW TOKEN OF CUSTOMER ...]のところに上記`card_...`を代入して、

```
curl https://api.omise.co/charges \
  -X POST \
  -u skey_test_5b3qijmoomindayotit: \
  -d 'amount=100' \
  -d 'currency=jpy' \
  -d 'card=card_test_5bmoomq3dmxe4qa3d3'
```

これで手動課金ができます！

## スケジュールを作り直す場合

スケジュールの作り方の基本は[こちら](https://qiita.com/keisuganomise/items/1dce3977fba867b5a010)に譲りますが、例えば日ベースでの課金でやり直す場合、さっき手に入れた `cust_..` と `card_..` を代入して、

```
curl -s https://api.omise.co/schedules \
  -X POST \
  -u skey_test_5moomindayoc0htit: \
  -d "every=1" \
  -d "period=day" \
  -d "start_date=2018-03-04" \
  -d "end_date=2118-02-08" \
  -d "charge[customer]=cust_test_5b4w0loxfeq7moorjo6" \
  -d "charge[card]=card_test_5bmoomq3dmxe4qa3d3" \
  -d "charge[amount]=100" \
  -d "charge[description]=Membership fee"
```
こんな感じで実行することになると思います。

## 課金失敗の通知

webhookとかどうなるの問題ですが、もしダメダメカードの顧客に課金をしようとして失敗した場合、webhookは、`charge.create`を投げてきます。その中のステータスが `failed` になっています。

ちなみに、ダッシュボード上だと下記のようになります。
<img width="823" alt="Screen Shot 2018-03-07 at 14.48.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/148353/c050b051-66da-85c3-5686-2edd0f26494f.png">

課金のリトライが最後まで失敗した場合、スケジュールのステータスは、`suspended`となります。

## まとめ

Schedule APIの課金は三回リトライされますが、それでもダメだった場合は、顧客にカード情報を更新してもらい、それで再度手動で課金する、ないしスケジュールを作り直す、もしくは顧客との契約をやめる....という選択肢がありました。

わからないことなどありましたら遠慮なくコメントしてください。