2024年2月5日月曜日

農業者による農業者のためのお金の話

最近、新規就農された方(研修中含む)と税金の申告や年金、保険、補助金といったお金まわりのことについて話す機会がありました。

やはりお金のこととなると気軽に相談できる機会も限られるようで、どうしていいか分からない部分がままあるようです。

そんなわけで、あくまでいちサンプルとしてですが、私の場合を簡単に書いてみようかと思います。(個人経営体で、法人化はしていません)

誰かの参考になれば幸いです。

(私の認識が間違っていることもあり得ますので鵜呑みにせず、ご自身でしっかり調べるようにしください)


【確定申告】
ちゃんとやる。

収入がない場合も、住民税の申告はちゃんとやる(→国民健康保険の軽減を受けるため)。

農業の収入が発生したら青色申告をする。要件を満たし65万円の控除を受ける。経営の状態をしっかり把握する。


【国民健康保険】
ちゃんと払う。

(世帯)収入が少ない場合は(世帯全員が)住民税の申告をしていれば軽減される。


【年金】
国民年金はちゃんと払う。払っていれば終身の老齢基礎年金だけでなく、万が一の場合には障害年金や遺族年金が受給できる。

払えないときは、確実に免除申請をする。免除申請をすれば受給資格を得られる。保険料の半分は税金による負担なので未納はただただ損。

増額手段は付加年金、農業者年金、iDeCo(個人型確定拠出年金)、国民年金基金、小規模企業共済から選択。それぞれ掛金の税制(社会保険料控除 or 小規模企業共済等掛金控除)や受給の仕方が異なるので、家族構成や年齢、働き方、好み等に合わせて検討。

私は、付加年金を納付の上で、iDeCoに加入しています。ただし、現状NISA(少額投資非課税制度)への積み立てを優先しているため(理由後述)iDeCoの掛金は最低額の5,000円にしています。

iDeCoは、加入年数によって一時金として受給する際の退職所得控除の額が決まるので、最低額からでも早めの加入が有利です。NISAの枠が(いつの日か)埋まったら退職所得控除の額と調整して掛金を増額予定です。


・参考


【NISA】
今年から始まった新しいNISAはぜひ活用したいところです。枠も従来より大幅に大きくなり、将来への積立の目標値としてちょうどいいです。

iDeCoは掛金が所得控除の対象となる点でNISAより有利ですが、私は急な資金需要への対策としてNISAを優先しています。


【保険】
青色申告をして収入保険に入る。その他、事業内容に応じて適宜入る。

さわの場合は、水稲関連の農機具共済や農産物加工をやっているのでPL保険に入っています。

その他、自動車保険、火災保険、個人賠償保険は必須として、生命保険や医療保険等については自身の状況に合わせて検討。一般論として入りすぎに注意。どういう状況に備えるものかを明確にすることが変な保険に入らないためのコツ。

ちなみに、保険に関してはネットで入れるものが良いとも限らず、代理店経由じゃないと加入できないものもあります。

・個人的に参考になった情報
センパイちゃんねる(YouTube のチャンネルなんですが、保険についての冷静な考え方がとても参考になります)


【補助金】
活用できる補助金は活用。ただし、将来の選択肢を制限されることもあるので取捨選択は必要。また補助金ありきで事業を作ると長続きしない(印象)。

私の場合、青年就農給付金(現在の農業次世代人材投資資金)は利用しませんでしたが、加工場を作るために市の補助金は利用しました。非常に助かりました。


【借入金】
以前は、農林水産省が出している「新たな農業経営指標」の借入金に関する2つの指標を目安にしていましたが、いつの間にかこの指標は使われなくなった(?)ようです。ページが消えていました。まあ、新たなと言いつつ、すでに10年以上前に作られたものですからね。

参考までに、指標では以下のようになっていました。

・売上高借入金比率
 計算式:借入金/収入 計
 望ましい水準:0~100%

・生産単位当たり借入金
 計算式:借入金/耕作している農地面積 合計
 望ましい水準:0~130 千円/10a

借入金に関する指標を2つとも満たすには、10a当たりの売上高が13万円より多いのか、少ないのかにより、どちらの望ましい水準の上限がキャップになるかを把握する必要があります。

多ければ「生産単位当たり借入金」の上限が、少なければ「売上高借入金比率」の上限がキャップになります。


【番外 - クラウドファンディング】
支援者の立場での感想として、公共性の高いものや、Kickstarter 的なプロジェクトは支援しやすい。資金の使い道に疑問を持ってしまうようなものは関わらない方がいい。素直に応援の気持ちで支援したものでも、その後の報告がない、というか活動がないと微妙な気持ちになる(信用が下がる)。


***

「生活費+将来への積立」で年間どのくらい必要かを具体的にすると、経営計画も立てやすくなりますね。そして、そのための適正規模の見極めこそ農業経営の肝だと思っています。

仕事ですから、結局はどのくらいの労働力を投入し、どのくらいのお金の出る入るを目指すかが重要です。その実現に向けた仕組みとして最適な規模はどの程度なのか。ここをピタッと決められると安定感が出てくると思います。


雪だるま猫

2024年1月27日土曜日

徒然じゃない日々(28)

長らくのデフレによって(?)すっかり麻痺していたけれど、農業も経営なのだから経済や物価の状況変化にしっかり対応できる形にしておかないといけないなと、という思いが強くなっています。

物価高による経費増で利益圧迫 → 値上げするも販売数が減少して売上低下 → 財務悪化 → 運転資金確保のため値下げして販売 → 赤字転落 → 状況打破のため借入れ → 効果的な投資ができず一層のジリ貧

……こうした嫌なシナリオをいかに避けるかということですね。


財務基盤の安定性を高めておく。多角化によってリスク分散を図っておく。消費者の方に割安だと思ってもらえるよう商品価値を高め、将来的な値上げ余地をつくっておく。

こうした地道な足場固め、準備を日頃からコツコツやっていかねばと改めて思うところです。


***

そういえば、Amazon アソシエイトの画像リンクが使えなくなったようで、ブログ内にペタペタ貼ってあるものを修正しなければなりません……めんどい(笑)。


きな粉とキクラゲ(猫)

2024年1月3日水曜日

2024年、本年もどうぞ宜しくお願い致します。

遅ればせながら、明けましておめでとうございます。

年明けから、とんでもない出来事が立て続けに起こり、気持ちの作り方が難しい年の始めとなりました。

いつどこでどんな災害が起こるのか、本当に分からない時代です。いつ自分の身に振りかかかるかも分かりません。今できることは少ないですが、せめて寄付はしようと思います。


さて、せっかくの新年ですから、いろいろと整理整頓もしていこうと思います。

まずは仕事の方で、今までふんわりとやったりやらなかったりしていた部分をはっきりさせて、やらないことはやらないと決めていきたいと思います。

マンパワーの限られるわれわれのような小規模農家は、できることを増やすよりも、やらない方がいいことを明確にしていくことがより重要であるように感じています。

もちろん、チャレンジは必要ですが、土台とリスクコントロールがあってこそ有意義なものとなります。

あと比較的どうでもいいことですが、Twitter 改め X の方は更新を休止して、スマホからアプリを消してみました。Facebook や Instagram をやめた後も思いましたが、SNS って、意外と時間と思考を消費していますからね。別のことに時間を使おうと思います。

まぁ、と言いつつ、Bluesky にアカウントを作りました(笑)。Bluesky はログイン無しで閲覧可能になったのがいいですね。


それでは、今年もどうぞ宜しくお願い致します。皆様にとって、安穏とした一年となりますように。


きな粉とキクラゲ

2023年12月18日月曜日

徒然じゃない日々(27)

今年のはじめ頃に以下の投稿をしまして、3つの経営方針を決めました。


1年弱ほど経ちましたので、状況を確認してみました。


方針① - 自己資金の範囲で経営しつつ、効率化を進め、資金や労働力の余力を作り出していく。

→ 物価高による経費増、その他突発的な出費等により、資金的な余力を作ることはなかなか難しかったです。運転資金を厚めに確保しておく(売上高現預金比率50%の維持)という目標を立てているものの、現状まだまだ遠いです。

一方で、労働力≒時間的な余力は、小さな業務改善の積み重ねが効いてきて、それなりに作り出すことができてきました。


方針② - 余力をまた新たに農業、もしくは農業以外の事業に投下。ただし、状況の変化があったときに総崩れにならないように分散を意識する。

→ 空いた時間を利用してプログラマーを始めました。ご依頼を頂き、業務アプリ等を作っています。これには、自作スマート農業をやってきた経験が大変活きました。プログラミング技術はもちろんですが、自身が実務の中で使うものを作ってきたので、使う側の気持ちを考えながらものを作ることができる、というのは強みになっている気がします。

また、プログラマーは、時間を使うのみで資金的な投下がほぼ必要ないので、リスク分散という面でも小規模な農業との相性は良さそうに思います。


方針③ - 新たな事業は、あらかじめ撤退ラインを決めておく。状況によっては、一時的に休むという選択肢も準備しておく。

→ プログラマーとしては、時間の余裕さえあれば働くことができるので、基本的には撤退ラインは考えず、自身のできる範囲で働くという形でよさそうです。

資金を投下する場合には、明確な撤退ラインを決めます。


現状、このようなところです。

作業1つひとつの無駄を省き、コツコツと効率化を進めてきたことで、時間に関しては、それなりのゆとりを生み出すことができるようになってきました。

さわのような小規模家族経営の農家にとっては、総労働時間に対する利益額が重要な指標だと考えています。率ではなく額です。

もちろん、品目ごとには利益率を把握して調整しないといけませんが、経営全体を見る場合は、率で考えてもあまり意味がないように思っています。


また今後、新たに資金を投下するような事業は、少なくとも売上高現預金比率50%を固く維持できるようになってからが安全そうな気がしています。

農業をやっていく上で絶対に避けたいシナリオは、

経費上昇と売価下落による利益圧迫→売上増やさなきゃ→資金を借り入れて規模拡大→さらなる経費上昇と売価下落、加えて人件費増と返済負担増によってますますジリ貧……

となることです。

そうならないためにも、早め早めの準備と、日々慎重な舵取りをしていこうと思います。


***

ということで、師走も後半戦ですね。

バタバタですが、穏やかな年末年始を過ごすためにも、しっかり乗り切りたいと思います!


猫玉

2023年12月8日金曜日

農業経営研修会をやりました(2023年12月)

先週末、所属している団体で、農業コンサルタントの佐川友彦氏をお呼びしての農業経営研修会をやりました。

もともと団体の中で、経営改善のためにみんなで何かしたい!でも何をしたらいいかわからない!という状態が続いていました。

もちろん、わからないなりにも、あーでもないこーでもないと話し合い、青色申告ができるようになろうとか、先輩農家を質問攻めにしようとか、少しずつ動いていました。

そんな中で、実績あるプロの話を聞いてみたいという声があがり、今回の研修会を実施することになりました。


佐川氏については以前、著書も読ませていただきましたが、今回の研修会も得るものの多い会となりました。



 


やはり、われわれのような小さな経営体にとっては、ちょっとした工夫や改善の積み重ねが非常に重要であり、その実行体制をいかに作るかが肝であるということのように思いました。

実行した工夫や改善は、佐川氏の場合であれば「阿部梨園の知恵袋」として一般公開していますが、例えば団体等の中で Wiki のような形で共有したり、というのもありなのかなと思いました。ひとりで悶々と取り組むよりは遥かに捗りそうな気がします。

さらに、農業経営版の Qiita 的なもの?もしくは、まとめページ的なもの?があったら便利なのになーと思ったり。

質疑応答でも、こちらのふわりとした質問に対して、事例紹介を交えつつ、切り口をしぼり、とても分かりやすくご回答していただき、大きな学びとなりました。


(写真撮り忘れという致命的ミス)

2023年11月27日月曜日

【定期】家計の見直し(2023年11月)

久しぶりの家計の見直しです。

経営と家計が一体化した家族経営では、家計の最適化が心強い生存基盤になります。翻って、家族や自身にとって本当に価値あるものが何なのか、確認する良い機会になります。


・家電を買うのは難しい


最近、洗濯機と冷蔵庫の調子が悪くなって買い直したのですが、いやはや、どの商品にするのか、どこで買うのか、どういう割引やキャンペーンが使えるのか、選択肢があり過ぎて考えるのが大変ですね。

ていうか、そもそもなんですが、家電て高いんですね……。頓着がなさ過ぎて、昔ひとり暮らしを始めた頃の家電一式10万円!みたいなイメージで止まっていました(笑)。

ひとまず、複数店舗とサイトを見比べて、予算内で必要な機能を満たしたものは買えた気がしますが……まぁ、欲はかかずに、問題なく使えるものが買えたならよしとしましょう。

ちなみに、Haier の洗濯機と AQUA の冷蔵庫を買いました。

・追記
特に冷蔵庫は、今までのものが非常にイマイチだったので、生活の質がぐっと上がりました!上を見ればキリがありませんが、かといって安さだけを求めるよりは、ある程度のバランスで選んだ方が幸福度は上がりそうというのが今回の学びです(遅)。


・いよいよ新NISAが始まる


いよいよ新NISAが始まりますね。少しずつでもやらないよりはマシなので、みちみちと積み立てていく予定です。

SBI証券での積立設定もすでにできるようになっていたので、忘れないうちにやっておきました。

農業という波のある職業であればこそ、やはりこうした有利な制度は積極的に利用して、いざという時のために備えていきたいですね。

一応、iDeCoの方も加入年数確保のために最低掛金でやっていますが、いざというときに資金を用意できるようにと考えると、どうしてもNISAが優先になってしまうのですよね。


・考えても仕方ないけど円の先行きとか


最近、米ドル/円が150円あたりをうろうろしていますが、この先どうなるのですかね。

ユーロ/円も160円を超えていて、以前の感覚でこの相場を見てしまうと海外旅行もちょっと尻込みしてしまいます。

このまま円が弱くなるなら何か輸出とかしたくなりますね。乾椎茸とか。

物価高で厳しいからもっと補助を出してほしいという話も時々聞きますが、そうするとよりインフレ、より円安という方向に圧力がかかるわけで(実際はもっと複雑なのでしょうけど)、構造を変えない限りはより大変になっていくんじゃないかという気がします。経済って難しいですね。


・昨今の雑感


そんなこんな日々ですが、心穏やかに過ごすためには、(生計を共にする)家族での価値観の共有はとても大切だなと思うところです。

もちろん、完全一致なんてことはまず無理ですが、お互いが協力すればそれぞれ納得できる形は作れるんじゃないかと思います。

また、その合意形成(と言うと大げさですが)に向けた行動自体に、けっこう大きな意味があるように思います。


犬と猫(コケモモときな粉)

2023年11月24日金曜日

Google フォームで決済機能付き注文フォームを実現する

Google フォームと GAS(Google Apps Script)で、クレジットカードによる決済機能付きの注文フォームを無料&サーバーレスで実現します。

データベース代わりとしてスプレッドシート、確認メール等の送信に Gmail、オンライン決済には PAY.JP を使用します。

(システムの構築・運用は無料ですが、決済手数料はかかります)


注文を受けた際の流れは以下のようになります。丸数字付きの太字が注文者の動き、その下がシステムの動きの簡単な説明になります。

-----
① Google フォームから注文
→ Google フォームの回答をスプレッドシートに記録。決済ページのリンクを記載した確認メールを生成・送信する。

② 決済ページにアクセス
→ メールアドレス、注文合計金額が表示された決済ページを生成する。決済ページには有効期限を設定。

③ 決済
→ PAY.JP から Webhook を受け取り、スプレッドシートに決済完了を記録。決済完了のメールを生成・送信する。

④ 注文完了
-----

今回、在庫管理の部分については省いて作成しています。在庫管理については、「【GAS】Google フォームとスプレッドシートを連携して選択肢を動的に入力する。そして注文フォームを作ってみる。」を参考にしてみてください。

また、GAS によるシンプルな PAY.JP の導入については「GAS でオンライン決済 PAY.JP を導入する」をご参照ください。


***

それでは、以下 Google フォームを使用した決済機能付き注文フォームの作り方になります。PAY.JP のアカウントは作成済みの想定です(テストモードを使用)。

(決済に関するものですので、一応こうやると動くよという参考程度のものとしてください。ご利用の際は自己責任でお願いします)


1)注文フォームの作成


まずは、Google フォームで簡単な注文フォームを作成します。とりあえず、今回は以下のようにしました。

1) 商品 [プルダウン] ※1~複数個
2) お名前 [記述式](必須)
3) メールアドレス [記述式](必須)
4) お電話番号 [記述式]
5) 住所(続き) [記述式](必須)
6) お支払い方法 [ラジオボタン](必須)



2)回答を記録するスプレッドシートの加工


フォームの回答先となる新しいスプレッドシートを作成します。

「フォームの回答 1」シートの末列に「決済金額」「固有番号」「有効期限」「顧客番号」「決済状況」の5つの列を追加します。

「商品情報」という名前のシートを追加、A列に「商品名」、B列に「価格」を入力しておきます。その際、1行目は項目名として使用し、2行目から追記します。フォームの商品数、順番とそろえてください。



3)注文フォームから注文受付処理用の GAS を作成


フォームから GAS を作成します。フォームから作成することで、トリガー作成時に、イベントのソースとしてフォームを選択することができるようになります。

「注文受付.gs」
const sp = SpreadsheetApp.openById("スプレッドシート ID");
const sh = sp.getSheetByName("フォームの回答 1");
const lastRow = sh.getLastRow();
const lastCol = sh.getLastColumn();
const item_sh = sp.getSheetByName("商品情報");

function processOrder(e) {
  Utilities.sleep(5000);

  FormApp.getActiveForm(); // フォームへのパーミッションを与えるためのおまじない
  const res = e.response.getItemResponses();

  // 商品情報の取得
  const itemCount = item_sh.getLastRow() - 1;
  const item = item_sh.getRange(2, 1, itemCount, 2).getValues();

  // 金額合計を計算
  let amount = 0;
  for(let i = 0; i < itemCount; i++) {
    if(res[i].getResponse() != "") {
      amount += res[i].getResponse() * item[i][1];
    }
  }
  sh.getRange(lastRow, lastCol - 4).setValue(amount); // 合計金額を書き込み

  const id = generateId(); // 固有番号を生成
  sh.getRange(lastRow, lastCol - 3).setValue(id); // 固有番号を書き込み

  // 固有番号の有効期限を計算
  const timeStamp = new Date(sh.getRange(lastRow, 1).getValue());
  let expirationTimestamp = new Date(timeStamp.setHours(timeStamp.getHours() + 1));
  expirationTimestamp = Utilities.formatDate(expirationTimestamp, "JST", "yyyy/MM/dd HH:mm:ss");
  sh.getRange(lastRow, lastCol -2).setValue(expirationTimestamp); // 有効期限を書き込み

  sendConfirmationEmail(res, itemCount, item, amount, id); // 確認メールの送信
}
1行目の「スプレッドシート ID」を書き換えてください。

processOrder 関数を、フォーム送信時に起動するようにトリガー設定をしておきます。


「固有番号の生成.gs」
function generateId() {
  const previousIdSet = sh.getRange(2, lastCol - 3, lastRow - 1, 1).getValues(); // 生成済みの固有番号を取得

  let id;
  let isDuplicate;

  do {
    id = generateRandomString(); // ランダムな文字列を生成
    isDuplicate = checkForDuplicate(id, previousIdSet); // 重複をチェック
  } while (isDuplicate);

  return id;
}

function checkForDuplicate(value, previousValue) {
  for (var i = 0; i < previousValue.length; i++) {
    if (previousValue[i][0] === value) {
      return true; // 重複が見つかった場合
    }
  }
  return false; // 重複がない場合
}

function generateRandomString() {
  const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  let randomString = "";
  for (var i = 0; i < 30; i++) {
    let randomIndex = Math.floor(Math.random() * characters.length);
    randomString += characters.charAt(randomIndex);
  }
  return randomString;
}


「確認メールの送信.gs」
function sendConfirmationEmail(res, itemCount, item, amount, id) {
  // メール本文の作成
  const name = res[itemCount].getResponse(); // お名前
  const email = res[itemCount + 1].getResponse(); // メールアドレス
  const phone = res[itemCount + 2].getResponse(); // お電話番号
  const address = res[itemCount + 3].getResponse(); // 住所
  const payment = res[itemCount + 4].getResponse(); // お支払方法

  let order = [];
  for(let i = 0; i < itemCount; i++) {
    order.push(item[i][0] + " " + res[i].getResponse() + "個");
  }
  let body = name + " 様\n\nご注文ありがとうございます。\n\n以下の内容でご注文を承りました。ご確認ください。\n\n-----\n" + order.join("\n") + "\n\n合計 ¥" + amount + "\n-----\n\nお届け先住所:" + address + "\n電話番号:" + phone + "\n\nお支払い方法:" + payment;

  if(payment == "クレジットカード") {
    const url = "ウェブアプリの URL";
    body += "\n\nご注文内容を確認の上、以下リンク先よりお支払いを完了してください:\n" + url + "?id=" + id;
  } else if(payment == "銀行振込") {
    body += "\n\nご注文内容を確認の上、以下振込先へのお支払いを完了してください:" + "\n〇〇銀行\n△△支店\n普通 ×××××××";
  }
 
  // 確認メールの送信
  GmailApp.sendEmail(
    email,
    "ご注文内容の確認と決済ページのご案内",
    body,
    {
      from: "送信元メールアドレス",
      name: "送信者名"      
    }
  );
}
28行目の「送信元メールアドレス」、29行目の「送信者名」を書き換えてください。

16行目の「ウェブアプリの URL」は後ほど書き換えます。


「決済ページの生成.gs」
function doGet(e) {
  const PUBLISH_KEY = "公開鍵";
  const html = HtmlService.createTemplateFromFile('index');

  // URL から固有番号を取得
  const id = e.parameter["id"];
  if(id == null) {
    return HtmlService.createHtmlOutput("<center>無効な URL です。(1)</center>"); // 固有番号がない場合はエラーページを表示
  }

  // 対象の固有番号の行を特定
  const idSet = sh.getRange(2, lastCol - 3, sh.getLastRow(), 1).getValues();
  let rowNum = 0;
  for(let i = 0; i < idSet.length; i++) {
    if(id == idSet[i]) {
      rowNum = 2 + rowNum + i;
      break;
    }
  }
  if(rowNum == 0) {
    return HtmlService.createHtmlOutput("<center>無効な URL です。(2)</center>"); // 有効な固有番号がない場合はエラーページを表示
  } else if(sh.getRange(rowNum, lastCol).getValues() != "") {
    return HtmlService.createHtmlOutput("<center>無効な URL です。(3)</center>"); // 既に決済完了している場合はエラーページを表示
  }

  // 該当の注文を取得
  const order = sh.getRange(rowNum, 1, 1, lastCol - 1).getValues();

  // 有効期限のチェック
  const expirationTimestamp = order[0][lastCol - 1]; // 有効期限
  const currentTime = new Date(); // 現在時刻
  if(expirationTimestamp < currentTime) {
    return HtmlService.createHtmlOutput("<center>有効期限を過ぎた決済ページです。(4)</center>"); // 有効期限を過ぎた場合はエラーページを表示
  }

  // 決済情報
  const email = order[0][lastCol - 9]; // メールアドレス
  const amount = order[0][lastCol - 5]; // 決済金額

  // 決済情報を index.html へ
  html.rowNum = rowNum;
  html.email = email;
  html.amount = amount;
  html.PUBLISH_KEY = PUBLISH_KEY;

  return html.evaluate();
}

// PAY.JPでの決済処理
function doPost(e) {  
  const card = e.parameter["payjp-token"];
  const rowNum = e.parameter["rowNum"];
  const amount = e.parameter["amount"];
  const SECRET_KEY = PropertiesService.getScriptProperties().getProperty("SECRET_KEY");

  let customer =  UrlFetchApp.fetch("https://api.pay.jp/v1/customers", {
    "method" : "post",
    "payload" : {
      "card": card
    },
    "headers": {'Authorization': "Basic " + Utilities.base64Encode(SECRET_KEY + ":")}
  });
  customer = JSON.parse(customer);

  sh.getRange(rowNum, lastCol - 1).setValue(customer.id); // 顧客番号を書き込み

  UrlFetchApp.fetch("https://api.pay.jp/v1/charges", {
    "method" : "post",
    "payload" : {
      "amount": amount,
      "currency": "JPY",
      "customer": customer.id
    },
    'headers' : {'Authorization': "Basic " + Utilities.base64Encode(SECRET_KEY + ":")}
  });

  return HtmlService.createHtmlOutput("<center>" + amount + "円のお支払いが完了しました。<br /><br />ブラウザを閉じてください。</center>");
}
2行目の「公開鍵」を書き換えてください。公開鍵は、PAY.JP のテスト公開鍵になります。

54行目の「SECRET_KEY」は、「プロジェクトの設定」の「スクリプト プロパティ」に保存してください。値は、PAY.JP のテスト秘密鍵になります。


「index.html」
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <base target="_top">
    <title>注文フォーム 決済ページ(テスト)</title>
  </head>
  <body>
    <center>
    <h1>注文フォーム 決済ページ(テスト)</h1>
    <p>メールアドレスと決済金額をご確認の上、お支払いを完了させてください。</p><br />
    <form action="ウェブアプリの URL" method="post">
      <input type="hidden" id="rowNum" name="rowNum" value=<?= rowNum?> />
      <label>メールアドレス: <?= email?><input type="hidden" id="email" name="email" value=<?= email?> /></label><br />
      <label>決済金額: <?= amount?><input type="hidden" id="amount" name="amount" value=<?= amount?> /></label><br /><br />
      <script src="https://checkout.pay.jp/" class="payjp-button" data-key=<?= PUBLISH_KEY?>></script>
    </form>
    </center>
  </body>
</html>
12行目の「ウェブアプリの URL」は後ほど書き換えます。


コードが一通りできたら、デプロイをします。

GAS の「デプロイ」→「新しいデプロイ」から「種類の選択」で「ウェブアプリ」を選択。

・説明:任意
・ウェブアプリ / 次のユーザーとして実行:自分
・ウェブアプリ / アクセスできるユーザー:全員

として、「デプロイ」を実行。

「確認メールの送信.gs(16行目)」と「index.html(12行目)」の該当箇所を表示されたウェブアプリの URL に書き換えます。

「デプロイ」→「デプロイの管理」から編集で「新バージョン」を再度デプロイします。


4)Webhook 受取用の GAS の作成


PAY.JP からの Webhook 受取用に、決済ページとは別の URL を持たせたウェブアプリが必要となるため、新たな GAS を作成します。こちらの GAS はスプレッドシートから作成しても、紐づけなしで作成しても構いません。

「webhook受取.gs」
const sp = SpreadsheetApp.openById("スプレッドシート ID");
const sh = sp.getSheetByName("フォームの回答 1");
const lastRow = sh.getLastRow();
const lastCol = sh.getLastColumn();

function doPost(e) {
  const contents = JSON.parse(e.postData.contents);

  // Webhook の確認
  const cusSet = sh.getRange(2, lastCol - 1, lastRow - 1, 2).getValues(); // 顧客番号一覧の取得

  // 顧客番号が存在するかの確認
  const cus = contents.data.customer; // 顧客番号
  for(let i = 0; i < cusSet.length; i++) {
    if(cusSet[i][0] == cus) {
      if(contents.type == "charge.succeeded") {
        sh.getRange(i + 2, lastCol).setValue("完了"); // 決済完了を書き込み
        paymentConfirmationEmail(i + 2); // 決済完了メールの送信
      } else {
        sh.getRange(i + 2, lastCol).setValue(contents.type); // 決済失敗
      }
    }
  }
}
1行目の「スプレッドシート ID」を書き換えてください。

※ Webhook のヘッダーに含まれている X-Payjp-Webhook-Token を、GAS の doPost 関数で取得する方法がわからなかった(できない?)ので、顧客番号で Webhook の正当性を判断する形にしています。


「決済完了メールの送信.gs」
function paymentConfirmationEmail(rowNum) {
  const name = sh.getRange(rowNum, lastCol - 9).getValue();
  const email = sh.getRange(rowNum, lastCol - 8).getValue();

  let body = name + " 様\n\n決済が完了しました。\n\n発送まで今しばらくお待ちください。\n\nどうぞ宜しくお願い致します。";

  // 決済完了メールの送信
  GmailApp.sendEmail(
    email,
    "決済が完了しました",
    body,
    {
      from: "送信元メールアドレス",
      name: "送信者名"      
    }
  );
}
13行目の「送信元メールアドレス」、14行目の「送信者名」を書き換えてください。


こちらもデプロイをします。

GAS の「デプロイ」→「新しいデプロイ」から「種類の選択」で「ウェブアプリ」を選択。

・説明:任意
・ウェブアプリ / 次のユーザーとして実行:自分
・ウェブアプリ / アクセスできるユーザー:全員

として、「デプロイ」を実行。

ウェブアプリの URL をメモしておきます。

こちらの URL は知られないように管理してください。


5)PAY.JP で Webhook の設定

PAY.JP の「API設定」で、Webhookの追加をします。URL は、先ほどメモした Webhook 受取用の GAS のウェブアプリの URL となります。


これで完成となります。


***

思ったよりも手順が多くなりました。実際に運用する際には、もう少しエラーハンドリング等しっかりやる必要がありそうですが、とりあえず、一通り動くものができてよかったです。