6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

この記事では教育プラットフォームであるMentaの売上情報をダウンロードし、売上情報として会計サービスfreeeのAPIを利用し登録する流れを解説したいと思います。まだ連携先として追加されていない他のECサービス・収益化が行える配信サイト等を利用している方の参考にもなるかと思います。

Mentaとはプログラミング技術などを教える人・学習する人をマッチングするサイトです。私も1年以上メンターを行っており、累計50人近くの方に学習のサポートを行っています。現在調べてみたところ、このサイトの収益情報を自動登録できる会計ソフトは存在しないようなので、APIを通じてfreeeへ取引を登録するための簡単なCLIを作成しました。

成果物はこちらに掲載しています。

freeeでの収支の入力方法を理解する

Mentaは入金から30日以上経過した売上について出金の申請が行なえます。契約者毎に支払タイミングが異なり、毎月支払いが行われるので手動で入力するのはかなり手間です。売上が発生したタイミングでは会計ソフト上で売上を登録します。売上金については一旦Mentaへ銀行口座のように貯められるものと扱い、出金を行ったタイミングでMentaの口座から自分の口座に振替を行うという風に扱います。

売上に対しての手数料は20%に対して消費税を加算した22%で、出金の手数料は1回あたり300円必要です。

image.png

参考までにMenta上でダウンロードできる売上情報はこのようなCSVフォーマットでダウンロード可能です。

契約No,日付,ユーザー名,プラン名,招待割引,キャンセル,金額,手数料,売上額,出金日
11111,2021-09-24,Example User,"プラン名",,,1000,220,780,"2021-11-10 12:34:56"
22222,2021-10-24,Example User,"プラン名",,,1000,220,780,

取引入力の前に、Menta上の残高を登録するための口座を作成する必要が有ります。「決済サービス・電子マネーを登録する」→「利用しているPOSレジ・ESサイト(出店)・決済サービスが見つからない場合」と遷移することで、手動で入力が可能な空の口座が作成できます。

image.png

freeeでの収支入力の例です。

image.png

今回の例は1000円の契約に対しての売上があり、それに対して220円の手数料が運営側に差し引かれています。収入で売上高として1000円を入力し、マイナス収入として手数料の220円を差し引いています。そして、その合計金額780円と同額をMentaの口座で受け取りします。また、備考欄にてMentaでの注文IDを記述しています。

これから取引情報をAPI経由で投げ込んでいきます。一旦Web画面から入力しておくと、Developer Toolsでの通信内容などを覗くことで、APIで投げるJSONペイロードの形がわかりやすくなるのでオススメです。

freeeでOauth2認証を行う

APIを呼び出すまでの手順はこちらで解説されています。開発者アカウントは無料で登録することができ、一定期間毎にデータが消去されるようです。登録が完了すると事業者IDとアクセストークンが画面に表示されます。

freee API スタートガイド https://developer.freee.co.jp/getting-started

しかしながら、今回は開発者アカウントではなく個人事業主として作成した事業所でのAPI呼び出しを行いたいです。この場合はスタートガイドに記述されていない方法を利用する必要がありました。アカウント作成時に表示されたアクセストークンは一定時間で失効し、テスト環境の事業所でしか使えません。実際に本番利用するためには、Oauth2認証を利用し、ClientID,ClientSecretからアクセストークンを取得する必要があります。具体的には以下の方法があります。

  1. サーバサイドでの標準的な方法。HTTPエンドポイントを立てて、そこにコールバックを受けてアクセストークンを取得。
  2. 組み込みブラウザ等で認証を行う際の方法。認証後の画面に直接アクセストークンが表示され、それをコピペ/自動読み取り等で認識を行う。リダイレクトURLに urn:ietf:wg:oauth:2.0:oob を指定する。 https://app.secure.freee.co.jp/developers/tutorials/3

今回は手元で動くCLIなのでどちらの方法でも構いませんが、より利便性が高くなるように1番の方法で、ローカルサーバを立ててアクセストークンを取得する方法を採用しました。以下のような流れでアクセストークンを取得することができます。’

  1. http://localhost:9999/ でサーバを起動
  2. ブラウザで https://accounts.secure.freee.co.jp/public_api/authorize へアクセス その際GETパラメータの redirect_uri=http://localhost:9999/ をつける
  3. freeeのサイト上でログイン等を行う
  4. http://localhost:9999/ にリダイレクトされる その際GETパラメータに code が付いている
  5. CLIが https://accounts.secure.freee.co.jp/public_api/token へGETリクエストを投げる その際GETパラメータに code をつけるとレスポンスでアクセストークンが受け取れる

freeeのOauthアプリケーションの作成方法はこちらで紹介されています。
https://app.secure.freee.co.jp/developers/tutorials/2

Oauth2の設定 oauth2.Config とサーバの待受ポートを渡すことで、HTTPサーバの起動からアクセストークンの取得までを行うコードはこちらです。再利用性が良さそうなので外から使えそうな形にしました。(実際のコードでは攻撃を防ぐためのstateの検証・エラーハンドリング等を行っていますが簡略化したものを掲載しています。)

oauth2.go
func Oauth2AuthorizeLocalServer(config *oauth2.Config, port int) (*oauth2.Token, error) {
	ctx := context.Background()

	// redirect to authorized page
	url := config.AuthCodeURL(state)
	open.Start(url) // ブラウザが起動する

	// ローカルサーバの起動・認証コードの受け取り
	code, _ := func() (string, error) {
		l, _ := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
		defer l.Close()
		quit := make(chan string)
		go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			query := req.URL.Query()
			w.Write([]byte(`auth success`))
			w.(http.Flusher).Flush()
			quit <- query.Get("code")
			
		}))
		return <-quit, nil
	}()

	oauth2Token, err := config.Exchange(ctx, code)
	return oauth2Token, err
}
利用例
func main () {
	oauth2Config := &oauth2.Config{
		ClientID:     os.Getenv("FREEE_CLIENT_ID"),
		ClientSecret: os.Getenv("FREEE_CLIENT_SECRET"),
		Endpoint: oauth2.Endpoint{
			AuthURL:  "https://accounts.secure.freee.co.jp/public_api/authorize",
			TokenURL: "https://accounts.secure.freee.co.jp/public_api/token",
		},
		RedirectURL: "http://localhost:9999/callback",
	}}
	token, _ := Oauth2AuthorizeLocalServer(oauth2Config, 9999)
	log.Println(token)
}

受け取った oauth2.Token 構造体にはAccess TokenやRefresh Tokenが入っているので、これを永続化して再利用すればログインを省略することができます。今回は json.Encoder を利用して secret.json に保存しました。

Goからfreee APIの呼び出し

対応APIは一部だけですが、Go言語向けのクライアントライブラリがサードパーティによって提供されています。今回はこれを使います。対応していないエンドポイントを利用したい場合は、 https://qiita.com/kamijin_fanta/items/67dfa90675f0d66c9670 で紹介したようにOpenAPI定義を利用したクライアント生成を行うのがオススメです。

READMEにも記載がありますが、Oauth2のClientID,ClientSecret,AccessToken等が有れば呼び出しが可能です。クライアント生成後には以下のような記述で取引をfreeeに登録することが出来ます。

details := []freee.DealCreateParamsDetails{
	// 取引の詳細
}
payments := []freee.DealCreateParamsPayments{
	// 関連する支払情報
}
client.CreateDeal(ctx, oauth2Token, freee.DealCreateParams{
	IssueDate: "yyyy-mm-dd",
	Type:      "expense",  // 収支区分 (収入: income, 支出: expense)
	CompanyID: companyId,
	Details:   details,
	Payments:  &payments,
})

Mentaの売上情報CSVをパース

先程お見せしましたが、Mentaはダッシュボードから以下のようなフォーマットのCSVを出力できます。

契約No,日付,ユーザー名,プラン名,招待割引,キャンセル,金額,手数料,売上額,出金日
11111,2021-09-24,Example User,"プラン名",,,1000,220,780,"2021-11-10 12:34:56"
22222,2021-10-24,Example User,"プラン名",,,1000,220,780,

Go標準ライブラリの csv.NewReader を利用することで簡単にパースを行うことが出来ます。難しいことは行っていないのですがコードは長いのでGitHubのリンクを貼り付けておきます。

パーサ: https://github.com/kamijin-fanta/freee-menta-importer/blob/master/parser.go
パーサのテスト: https://github.com/kamijin-fanta/freee-menta-importer/blob/master/parser_test.go

パースにより以下のような構造体のスライスが得られます。

model.go
type Sale struct {
	ContractNo     string // 契約No
	Date           string // 日付
	UserName       string // ユーザ名
	PlanName       string // プラン名
	Price          int    // 金額
	Fee            int    // 手数料
	Sales          int    // 売上額
	// ...省略
}

Mentaへ取引登録する

MentaのCSVから注文情報が取得できたので、あとは取引の登録をするだけです。先程作成したAPIクライアントで各種パラメータを埋めた構造体を投げます。descriptionに文字列を指定することで備考として確認が可能となるので、契約IDを指定しました。

cmd_sales.go
description := fmt.Sprintf("menta-contract=%s", sale.ContractNo)
details := []freee.DealCreateParamsDetails{
	// 売上の収入登録
	{
		TaxCode:       129,       // sales_with_tax_10: 課税売上10%
		AccountItemID: 429799547, // 売上高
		Amount:        int32(sale.Price),
		Description:   &description,
	},
	// 利用手数料の支出登録
	{
		TaxCode:       136,       // purchase_with_tax_10: 課対仕入10%
		AccountItemID: 429799593, // 販売手数料
		Amount:        int32(sale.Fee) * -1,
	},
}
payments := []freee.DealCreateParamsPayments{
	{
		Amount:             int32(sale.Sales),
		FromWalletableID:   walletableId,
		FromWalletableType: "wallet",
		Date:               sale.Date,
	},
}
client.CreateDeal(ctx, oauth2Token, freee.DealCreateParams{
	IssueDate: date,
	Type:      "income",  // 収支区分 (収入: income, 支出: expense)
	CompanyID: companyId,
	Details:   details,
	Payments:  &payments,
})

リクエスト後にfreeeのWeb UIを確認すると正常に登録されていることが確認できます。

image.png

今回利用した全体のコードはこちらに掲載していますので、興味があれば御覧ください。また、MITライセンスとしましたのでコードの再利用等も歓迎します。 https://github.com/kamijin-fanta/freee-menta-importer

所感

前回のアドベントカレンダーを試した際に便利だと感じたので、今年からは契約を行い個人事業主で実際に利用するユーザとして記事を書かせていただきました。(去年の記事: https://qiita.com/kamijin_fanta/items/67dfa90675f0d66c9670 )

数十件・数百件の売上情報を手入力するのは現実的では無いので、こういった個人でも気軽に利用できる形でAPIが提供されているのは非常に有り難いです。今後も自動入力したいデータが増えたら自作しようと思います。

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?