Google Apps Scriptで簡単ECサイトを作ろう

gasで頑張る人はいないのか、AIに聞いても中々手こずったの同じことしようとしている人向けに公開
実際に作ったサイトはhttps://x.gd/Ppm2Z
上記の管理用スプレッドシートhttps://x.gd/XSFmu


PAY.JPの決済方法
```js
    const url = 'https://api.pay.jp/v1/charges';
    const options = {
      method: 'post',
      headers: {
        Authorization: 'Basic ' + Utilities.base64Encode(paymentKeys.payjpSecret + ':'),
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      payload: {
        amount: String(orderData.total),
        currency: 'jpy',
        card: orderData.paymentToken,
        description: '商品購入'
      },
      muteHttpExceptions: true
    };
    const response = UrlFetchApp.fetch(url, options);
    const result = JSON.parse(response.getContentText());
    if (result.error) throw new Error(result.error.message);

```


stripeのカード決済
```js
    const customer = createStripeCustomer(orderData.email);
    if (customer.error) throw new Error(customer.error.message);

    const stripeUrl = 'https://api.stripe.com/v1/payment_intents';
    const base64Key = Utilities.base64Encode(paymentKeys.stripeSecret + ':');
    const webAppUrl = ScriptApp.getService().getUrl();

    const attachResponse = UrlFetchApp.fetch(`https://api.stripe.com/v1/payment_methods/${orderData.paymentToken}/attach`, {
      method: 'post',
      headers: {
        Authorization: 'Basic ' + base64Key,
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      payload: { customer: customer.id }
    });
    
    if (JSON.parse(attachResponse.getContentText()).error) {
      throw new Error('PaymentMethodのアタッチに失敗しました');
    }

    const intentOptions = {
      method: 'post',
      headers: {
        Authorization: 'Basic ' + base64Key,
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      payload: {
        amount: String(orderData.total),
        currency: 'jpy',
        customer: customer.id,
        payment_method: orderData.paymentToken,
        confirm: 'true',
        return_url: webAppUrl + '',
        setup_future_usage: 'on_session',
        'metadata[order_id]': orderNumber.toString()
      },
      muteHttpExceptions: true
    };

    const intentResponse = UrlFetchApp.fetch(stripeUrl, intentOptions);
    const intentResult = JSON.parse(intentResponse.getContentText());

    if (intentResult.error) throw new Error(intentResult.error.message);

```


stripeの銀行振込

```js
    const customer = createStripeCustomer(orderData.email);
    if (customer.error) throw new Error(customer.error.message);

    const stripeUrl = 'https://api.stripe.com/v1/payment_intents';
    const base64Key = Utilities.base64Encode(paymentKeys.stripeSecret + ':');
    const webAppUrl = ScriptApp.getService().getUrl();

    // Create and confirm PaymentIntent in one step
    const payload = {
      amount: String(orderData.total),
      currency: 'jpy',
      customer: customer.id,
      'payment_method_types[]': 'customer_balance',
      'payment_method_data[type]': 'customer_balance',
      'payment_method_options[customer_balance][funding_type]': 'bank_transfer',
      'payment_method_options[customer_balance][bank_transfer][type]': 'jp_bank_transfer',
      confirm: 'true',
      return_url: webAppUrl + '',
      'metadata[order_id]': orderNumber.toString()

    };

    const options = {
      method: 'post',
      headers: {
        'Authorization': 'Basic ' + base64Key,
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      payload: payload,
      muteHttpExceptions: true
    };

    const response = UrlFetchApp.fetch(stripeUrl, options);
    let result;
    try {
      result = JSON.parse(response.getContentText());
    } catch (e) {
      console.log('Stripe APIレスポンス:', response.getContentText());
      throw new Error('Stripe APIのレスポンスが不正です');
    }
```


stripのコンビニ決済

```js
    const customer = createStripeCustomer(orderData.email);
    if (customer.error) throw new Error(customer.error.message);

    const stripeUrl = 'https://api.stripe.com/v1/payment_intents';
    const base64Key = Utilities.base64Encode(paymentKeys.stripeSecret + ':');

    const intentOptions = {
      method: 'post',
      headers: {
        Authorization: 'Basic ' + base64Key,
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      payload: {
        amount: String(orderData.total),
        currency: 'jpy',
        customer: customer.id,
        'payment_method_types[]': 'konbini',
        payment_method: orderData.paymentToken,
        'metadata[order_id]': orderNumber.toString()
      },
      muteHttpExceptions: true
    };

    const intentResponse = UrlFetchApp.fetch(stripeUrl, intentOptions);
    const intentResult = JSON.parse(intentResponse.getContentText());

    if (intentResult.error) throw new Error(intentResult.error.message);


    // コンビニ決済の詳細を取得
    let konbiniDetails = null;
    if (intentResult.next_action && intentResult.next_action.konbini_display_details) {
      konbiniDetails = intentResult.next_action.konbini_display_details;
    }
    // モックデータを使用してコンビニ支払い情報を表示
    const mockKonbiniDetails = {
      convenience_store: 'lawson',
      expires_at: Math.floor(Date.now() / 1000) + 86400, // 24時間後
      payment_code: '1234567890',
      receipt_number: '1234-5678',
      hosted_instructions_url: 'https://pay.stripe.com/test_instructions'
    };
    konbiniDetails = konbiniDetails || mockKonbiniDetails

```

stripeのコンビニ決済はテスト環境ではコンビニ決済の詳細を返してくれないためモックを使っています。