Stripeを使った決済の払い戻しの実装

前回、Stripe APIを使用した支払い機能の実装方法を紹介しました。今回は、支払いの取り消し（払い戻し）機能を実装したので、その手順とコードをこちらで記載しておこうと思います。

# 今回実施する環境
バージョン: PHP 8.2.9
エディター: vsCode

# stripe_paymentsテーブルを作成
払い戻し機能の実装に際しては、まず支払い記録を保存するテーブルを作成する必要があります。このテーブルは、Stripeで行われた支払いに関する情報を保存し、後の取り消し処理にも役立ちます。そこで、まずはstripe_paymentsテーブルを作成し、支払い記録を保存する仕組みを作ります。
```
php artisan make:migration create_payments_table --create=payments
```
```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('stripe_payments', function (Blueprint $table) {
            $table->id('stripe_payment_id');
            $table->foreignId('appointment_id')
            ->nullable()    
            ->references('appointment_id')
            ->on('appointments')     
            ->onUpdate('cascade')
            ->onDelete('cascade');

            $table->string('charge_id');
            $table->decimal('amount', 10, 2);
            $table->string('customer_id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('stripe_payments');
    }
};
```
:::note info
appointment_id: ここでは、予約情報が保存されているappointmentsテーブルとStripeの支払い情報を関連付けるために、appointment_idを外部キーとして指定します。

charge_id: stripeの方で支払われた時に作成されるId
amount: 支払われた金額

customer_id: クレジット情報などをstripe側へ送信した際に発行されるId
:::

```
php artisan migrate 
```

# モデルを作成します

```
php artisan make:model StripePayment
```


```php:Models/StripePayment
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;


class StripePayment extends Model
{
    use HasFactory;
    protected $table = 'stripe_payments';    
    protected $primaryKey = 'stripe_payment_id';    

    protected $fillable = [
        'appointment_id',
        'charge_id',
        'amount',
        'customer_id'   
    ];
}

```
# stripe_paymentsテーブルデータの作成
支払い情報は、実際に予約が行われた時にstripe_paymentsテーブルに保存されるようにコードを記述していますが、その部分を説明すると本題から外れてしまいます。したがって、テストを行う際にはSeederを使用してデータを挿入し、動作を確認していただくことをお勧めします。

以下のようにデータを追加してテストを行ってください。
![スクリーンショット 2024-11-12 22.46.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3931703/bdf4a1a3-9a67-30e8-a9e0-10ca66f59fe5.png)





# 予約の削除
今回は、予約が削除された際にその予約に対する払い戻しを行いたいため、予約IDを引数として受け取り、該当する予約に対して払い戻し処理を実行します。支払い処理はAppointmentControllerでは行わず、専用のServices/DeleteAppointmentクラスを作成して、そこで払い戻しと予約の削除処理を行うようにしています。

```php 
class AppointmentController extends Controller
{
 protected $storePaymentDetails;
 
  public function __construct(DeleteAppointment $deleteAppointment,)    
    {
        $this->deleteAppointment = $deleteAppointment;
    }
    <!--予約の実装などがありますが省略します-->
    /**
     * 予約の削除
     *
     * @param integer $appId
     * @return void
     */ 
    public function deleteAppointment(int $appId)
    {
        $deleteAppointment = $this->deleteAppointment->deleteAppointment($appId);
        return response()->json($deleteAppointment);
    }
```
# 払い戻し処理を行うファイルを作成
```
php artisan make:controller Services/DeleteAppointmentController

```

```php:Services/DeleteAppointmentController
<?php

namespace App\Services;
use App\Models\Appointment; 
use Stripe\Stripe;  
use Stripe\Refund;  //支払いの処理を行う
use App\Models\StripePayment;   
use Exception;  

class DeleteAppointment
{
    /**
     * 予約の削除
     *
     * @param integer $appId
     * @return \Illuminate\Http\JsonResponse
     */ 
    public function deleteAppointment($appId)
    {
        $appointment = Appointment::query()->find($appId); 
        if ($appointment) {
        
            //支払い情報の取得
            $payment = StripePayment::query()->where('appointment_id', $appId)->first(); 

            try {
                if ($payment) {
                    $this->processRefund($payment);   
                }

                //予約の削除
                $appointment->delete();

                return response()->json(['message' => 'Deleted successfully',
            'refund_id' => $payment ? $payment->charge_id : null]); 
            } catch (Exception $e) {
                return response()->json([
                    'status' => 'error', 
                    'message' => 'Refund failed', 
                    'error' => $e->getMessage()
                ]);
            }
        } else {
            return response()->json(['message' => 'Record not found']);
        }
    }

     //ここでpaymentの情報を受け取り支払いの処理を行います。
    /*
     * 返金処理
     *
     * @param StripePayment $payment
     * @return void
     */ 
    private function processRefund($payment)
    {
        Stripe::setApiKey(env('STRIPE_SECRET'));

        $refund = Refund::create([
            'charge' => $payment->charge_id,
        ]); 
    }
}
```
実装の概要
今回の実装では、予約（Appointment）が削除された際に、関連する支払い情報を払い戻しする処理を行いました。予約削除時に払い戻しが必要なケースを想定し、StripeのAPIを利用して返金処理を行っています。実際には、予約に紐づく支払い情報があればその払い戻しを実行し、その後予約を削除する流れです。

もし予約以外のトリガーで支払い処理を行いたい場合は、Appointmentに関連する部分を別のモデルに置き換えていただければ、同様の処理が可能です。例えば、注文や購買など、他のシステムでも応用できると思います。