Laravel PAY.JP로 결제 가능

14074 단어 api결산pay.jpLaravel
개시하다
지불 대리 서비스 PAY.다음은 Laravel로 JP를 구현하는 방법에 대해 설명합니다.
실무결제 기능 심사를 맡아 스스로 정보를 정리하려고 기사를 썼다.
실제로 시행한 적은 없지만 내용 처리를 대충 알고 있기 때문에 이런 느낌이 있는 사람이 쓰는 전제에서 참고만 해주세요.
···그나저나 앞으로도 스트립, 페이페이페이, 지모페이의 결제 대행 서비스를 소개하고 싶어요!!
카탈로그
  • 사전 준비
  • 설치하다.
  • 컨덕터 확인
  • 표 디자인
  • 신용 결제 화면
  • 토큰 보존
  • 결제 확인 화면
  • 결산 처리
  • 최후
  • 사전 준비
    PAY.JP 계정 등록
    대시보드에서 공개 키 및 개인 키 확인
    PayJP에 로그인한 후 대시보드의 API를 통해 공개 키와 기밀 키를 확인합니다.
    그리고 env 파일에서 공개 키와 개인 키를 설명합니다.


    env 파일
    env 파일에 이전 계기판의 테스트 기밀 키와 테스트 공개 키를 기술합니다.
    공식 환경에서 공식 기밀 키와 공식 공개 키를 사용합니다.
    PAY_JP_SECRET="秘密鍵"
    PAY_JP_KEY="公開鍵"
    
    config/app.php
    여기 PAY 설정이야.JP 결제 시 처리에 사용됩니다.
    'aliases' => [
        // 秘密鍵
        'pay_jp_secret' => env('PAY_JP_SECRET'),
        // 公開鍵
        'pay_jp_key' => env('PAY_JP_KEY'),
    ],
    
    설치하다.
    compooser Pay를 사용합니다.JP를 설치합니다.
    composer require payjp/payjp-php
    
    컨덕터 확인
    먼저 결제 기능을 설치하기 전에 도선을 확인해야 한다.
    【도선】
    ① 상품 구매를 위해 "신용 결제 화면"으로 들어가세요
    ② "신용 결제 화면"에 신용카드 정보 입력
    ③'신용결제화면'에서'확인화면'버튼을 클릭→'결제확인화면'으로 이동
    ④ "결제 확인 화면"에서 "결제"버튼을 클릭
    ⑤ 결제처리완료, 성공→"결제완료화면"
    [내부 처리]
    제목 컨덕터 ②에서 신용카드 정보를 모두 →PAY로 입력합니다.JP API로 토큰을 발행하다.(js 처리)
    ※ 해당 토큰은 결제 시점에 한 번만 사용할 수 있습니다.반복해서 사용할 수 없다.
    input 탭 (type = "hidden") 의value에 제목에 발행된 영패를 삽입합니다.
    각 도선 ③ 화면 확인 버튼을 누르면 영패 정보를 결산표에 보관한다.
    ※'결제 완료 여부를 판단하는 열'은 가보존
    이후 결제가 잘 되면 진으로 변경됩니다.(컨덕터 ⑤)
    주도적 위치 ④'결제'버튼을 눌러 결제 처리를 수행하고 결제가 순조롭게 완료되면 결제표의'결제 완료 여부를 판단하는 열'이 진짜 형태로 저장된다.
    이에 따라 결제에 실패하면'결제 완료 여부를 판단하는 열'은 가짜로 유지되고 결제는 미완성 상태가 된다.
    표 디자인
    우선 최소한의 열만 구상해라.
    [Goods(상품)표의 각 열]
    /타이틀
    /price(가격)
    [Payments(결제)표의 각 열]
    ・goodsid(부모님의 상품 테이블로서의 id)
    ・paymentmethod_id(결제에 사용되는 영패/결제 후 고객 id)
    ・paymentis_finished(결제 완료 여부 판단)
    ① 신용 결제 화면
    신용정보를 입력해 결제상품의'확인 화면'버튼을 누를 때까지 처리하는 방법을 소개한다.
    카드 정보 후 결제용 토큰을 발행해 DB로 보내고 결제 확인 화면으로 전환한다.
    요점은 다음과 같은 세 가지가 있다.
    • 신용카드 입력 형식
     PAY.JP의 js로 작성된 입력 형식을 표시합니다.
    ・영패를 삽입한 입력 창
    type="hidden"의 input 태그에 PAY가 인쇄되어 있습니다.JP의 js로 만든 영패를 삽입합니다.
    ・PAY.JP 결제용 영패를 발행하는 js 처리
    영패의 발행, 입력 창의 생성, 입력 오류 처리를 보여 줍니다.
    // こちらはクレジット入力フォーム、トークンが挿入される入力フォーム、PAY.JPのjsを読み込む処理を記述しています。
    
    <h1>クレジット決済画面</h1>
    // クレジットカード情報入力フォーム
    <div>
      <div>
        <p>クレジットカード番号</p>
        // ここにjsで作成した入力フォームが入る。idを指定。
        <div id="number-form"></div>
        // ここにjsから送られてきたエラーメッセージが表示される。idを指定。
        <span id="number-errors"></span>
      </div>
    
      <div>
        <p>セキュリティコード</p>
        <div id="cvc-form"></div>
        <span id="cvc-errors"></span>
      </div>
    
      <div>
        <p>有効期限</p>
        <div id="expiry-form"></div>
        <span id="expiry-errors"></span>
      </div>
    </div>
    
    
    // トークン情報をDBに送信するフォームとボタン
    <form
      action="{{ route('user.goods.reconfirmationPayment', ['goods' => $goods]) }}" 
      method="post"
    >
         @csrf
    
      // valueにPAY.JPのAPIで作成したトークンを入れる
      <input type="hidden" name="payment_method_id" id="payment_method_id" value="">
      <button>確認画面へ</button>
    </form>
    
    // payjp.jsのAPIを使用する為に読み込む必要がある
    <script src="https://js.pay.jp/v2/pay.js"></script>
    // 決済時に使用するトークンを発行する処理
    <script src="{{ asset('/js/payjp-create-card-token.js') }}"></script>
    
    Pay.JP 결제 토큰을 발행하는 JS 파일
    이곳에서는 영패 발행과 입력 창 제작, 검증 오류 디스플레이 처리 등이 진행된다.
    이것은 정부의 참고이다.
    https://pay.jp/docs/payjs
    payjp-create-card-token.jsファイル
    
    // elementsインスタンス1つにつき、1組のカード情報入力フォームを用意できる。
    // ページ内に複数のフォームを作りたい場合は、その分だけelementsインスタンスを用意する。
    var elements = payjp.elements()
    
    // カード入力時にバリデーションエラーメッセージを出す為に、メッセージを表示する場所を指定
    var numberErrors = document.getElementById('number-errors');
    var expiryErrors = document.getElementById('expiry-errors');
    var cvcErrors = document.getElementById('cvc-errors');
    
    // create()の引数に設置したい項目(今回はcardNumber, cardExpiry, cardCvcのみ)を入力後、
    下のmount()で入力フォームをDOM上に配置します。
    // カード番号入力フォーム
    var numberElement = elements.create('cardNumber')
    // 有効期限入力フォーム
    var expiryElement = elements.create('cardExpiry')
    // CVC入力フォーム
    var cvcElement = elements.create('cardCvc')
    
    // この処理でHTML上に入力フォームが表示される
    numberElement.mount('#number-form')
    expiryElement.mount('#expiry-form')
    cvcElement.mount('#cvc-form')
    
    let numberElIsCompleted = false;
    let expiryElIsCompleted = false;
    let cvcElIsCompleted = false;
    
    // 入力内容に何らかのエラーがある際にエラーを表示する処理
    // 入力フォームの値が変更された事を検知してイベントが発火
    numberElement.on('change', (event) => {
        numberErrors.innerHTML = '';
        if (event.error) {
            numberErrors.innerHTML = event.error.message;
        }
        numberElIsCompleted = event.complete
    });
    expiryElement.on('change', (event) => {
        expiryErrors.innerHTML = '';
        if (event.error) {
            expiryErrors.innerHTML = event.error.message;
        }
        expiryElIsCompleted = event.complete
    });
    cvcElement.on('change', (event) => {
        cvcErrors.innerHTML = '';
        if (event.error) {
            cvcErrors.innerHTML = event.error.message;
        }
        cvcElIsCompleted = event.complete
    });
    
    // blurでフォーカスが外れると(入力フォームから離れると)下記イベントが実行される
    numberElement.on('blur', () => {
        if (numberElIsCompleted && expiryElIsCompleted && cvcElIsCompleted) {
            // payjp.createToken(対象のインスタンス)を作成する
            payjp.createToken(numberElement).then((response) => {
                if (response.error) {
                    // APIのレスポンスでエラーが返ってきた場合メッセージを送る
                    numberErrors.innerHTML = response.error.message;
                    return false;
                } else {
                    // APIのレスポンスでトークンを取得出来た場合、
                    // inputタグのtype="hidden"のvalueにトークンを挿入する
                    document.querySelector('#payment_method_id').value = response.id;
                    console.log(response);
                    numberErrors.innerHTML = ''
                };
            })
        }
    });
    expiryElement.on('blur', () => {
        if (numberElIsCompleted && expiryElIsCompleted && cvcElIsCompleted) {
            payjp.createToken(numberElement).then((response) => {
                if (response.error) {
                    expiryErrors.innerHTML = response.error.message;
                    return false;
                } else {
                    document.querySelector('#payment_method_id').value = response.id;
                    console.log(response);
                    expiry_errors.innerHTML = ''
                };
            })
        }
    });
    cvcElement.on('blur', () => {
        if (numberElIsCompleted && expiryElIsCompleted && cvcElIsCompleted) {
            payjp.createToken(numberElement).then((response) => {
                if (response.error) {
                    cvcErrors.innerHTML = response.error.message;
                    return false;
                } else {
                    document.querySelector('#payment_method_id').value = response.id;
                    console.log(response);
                    cvcErrors.innerHTML = ''
                };
            })
        }
    });
    
    
    ②영패의 보존
    여기 아까 만든 페이예요.JP의 영패를 Payments 표에 저장합니다.저장된 영패는 이후의 결산 처리에 사용될 것이다.
    이번에 계산서에서 산 물건은 보관하고 있어요.paymentis_finished는 가짜 물건을 구매 미완성으로 간주합니다.
    public function reconfirmationPayment(
      Goods $goods, 
      Payment $payments,
      Request $request
    )
    {
        DB::beginTransaction();
        try {
            // 支払い履歴のテーブルに各データを登録
                    // どの商品を購入したか識別する為、goods_idを保存
            $payments->goods_id = $goods->id;
                    // 先程payjp-create-card-token.jsファイルで発行したidを保存
            $payments->payment_method_id = $request->payment_method_id;
                    // このカラムをfalseで保存。後に決済が完了したもののみtrueに変更する。
            $payments->payment_is_finished = false;
            DB::commit();
        } catch (\Exception $e) {
            DB::rollback();
            throw $e;
        }
    
        return view('user.goods.reconfirmation_payment',['goods' => $goods]);
    }
    
    ③ 결제 확인 화면
    결제 전 확인 화면입니다.문제가 없으면 "결제"버튼으로 결제 처리합니다.
    <div>
      <h1>決済確認画面</h1>
    
      // 商品情報
      <div>
        <p>商品名</p>
        <p>{{ $goods->title }}</p>
      </div>
      <div>
        <p>決済金額</p>
        <p>{{ $goods->price }}円</p>
      </div>
    
      〜〜〜  その他商品情報を表示  〜〜〜
    
      // 商品を確認後、決済する場合はこちら
      <a href="{{ route('user.goods.payment', ['goods' => $goods]) }}">決済する</a>
    
      // クレジットカード入力画面へ戻る
      <a href="javascript:history.back()">戻る</a>
    <div>
    
    ④ 결산 처리
    결제 진행 중입니다.PayJP 클래스의 charge 방법으로 결제합니다.
    PayJP 클래스는 서비스 계층에서 처리를 설명합니다.PayJP 클래스의 처리는 아래에 기재되어 있습니다.
    ※ charge(), refund() 등의 방법은 페이JP입니다.php 파일을 씁니다.
    // Service層のPayJPクラスを使用する為、追記
    use App\Services\Payment\PayJP;
    
    public function payment(
      Goods $goods,
      Payment $payments,
      PayJP $payjp
    )
    {
       // PayJPクラスのcharge処理(決済処理)
       // 引数として、商品の金額とPAY.JPのjsで発行したトークンを記述
       $response = $payjp->charge($goods->price, $payment->payment_method_id);
       DB::beginTransaction();
       try {
                    // pi(payment intent id)は決済完了後に発行されるユニークなid
            // $responseのidとしてpiを返す
            // piを用いて、PayJPのダッシュボードで決済履歴を検索できる
            $payment->payment_method_id = $response->id;
            // 決済が完了したらpayment_is_finishedをtrueとして、決済成功となる
            $payment->payment_is_finished = true;
            $payment->save();
            DB::commit();
        } catch (\Exception $e) {
            DB::rollback();
            // 決済が失敗したら、決済代行サービス側の決済情報も削除させる処理
            $payjp->refund($response->id);
            throw $e;
        }
    
        // 購入完了画面へ遷移する
        return view('user.goods.purchased', ['goods' => $goods]);
    }
    
    PayJP 클래스
    [PayJP.php 파일 제작]
    서비스 층에서 PayJP 클래스를 만들려면 app 폴더 아래에 '서비스' 폴더를 만들고 그 아래에 'Payment' 폴더를 만드십시오.
    Payment 폴더에서 PayJP.php 파일을 작성하십시오.
    [설명 처리]
    ・charge
    개인 키를 설정하고 결제 처리를 실행합니다.
    ・refund 방법・결제 취소 처리
    디렉터가 DB에 대한 결제 정보 등록 처리에 실패할 경우 결제를 취소합니다.이것은 PayJP와 Payments 표의 일치성을 유지하기 위해 기술한 것이다.
    비밀 키를 설정하고retrieve () 방법으로 결제 정보를 얻은 후refund () 방법으로 결제를 취소합니다.
    // app/Service/Payment/PayJP.phpファイル
    
    <?php
    
    namespace App\Services\Payment;
    
    use Illuminate\Http\Request;
    
    class PayJP
    {
        public function charge($price, $payment_method_id)
        {
            // 事前準備のconfig/app.phpファイルで設定した秘密鍵をセットします
            \Payjp\Payjp::setApiKey(config('app.pay_jp_secret'));
            try {
                // 新規決済情報を作成する処理(決済)
                $result = \Payjp\Charge::create([
                    // $payment_method_idはpayjp.jsのAPIで発行されたトークン
                    "card" => $payment_method_id,
                    // 商品の金額
                    "amount" => $price,
                                    // 通貨の選択(今回は日本円="jpy")
                    "currency" => "jpy",
                ]);
            // 以下は各例外処理
            } catch (\Payjp\Error\Card $e) {
                throw $e;
            } catch (\Payjp\Error\InvalidRequest $e) {
                // Invalid parameters were supplied to Payjp's API
                throw $e;
            } catch () {
                // その他例外処理を記述してください...
            }
            return $result;
        }
    
        // 先程登録した決済情報を削除する処理(決済の取り消し)
        // DBに決済情報を保存する際に何らかの障害で保存出来なかった場合、
                  PAY.JP側の決済もキャンセルさせ、DBとの整合性を保つ
        public function refund($payment_id)
        {
            // 事前準備のconfig/app.phpファイルで設定した秘密鍵をセットします
            \Payjp\Payjp::setApiKey(config('app.pay_jp_secret'));
            // retrieveで支払い情報を取得します。
            // retrieveメソッドの引数にpi(charge後に返ってくるレスポンスのid)をセットします。
            $charge = \Payjp\Charge::retrieve($payment_id);
            // refundメソッドで支払いをキャンセルします。
            return $charge->refund();
        }
    }
    
    최후
    이번에 설명했지만 이 코드는 사실상 검증되지 않았기 때문에 약간의 수정이 필요하다고 생각합니다.
    (대략, 조금만 고치면 움직일 텐데...)
    그리고 업무 경험도 반년에서 1년 정도밖에 안 돼요. 더 좋은 방법이 있으면 꼭 알려주세요!!
    원래 테스트 코드에 모크를 사용해서 DI의 구조를 만드는 것이 좋겠다고 생각했다.
    앞으로도 스트립, 페이페이, 지모페이의 결제 대행 서비스를 소개합니다!!
    참고 자료
    【PAY.JP API】
    PAY.JP API의 공식 참조
    https://pay.jp/docs/api/
    【PAY.JP.js】
    영패 발행의 js 처리에 대한 공식 참고가 기재되어 있습니다
    https://pay.jp/docs/payjs

    좋은 웹페이지 즐겨찾기