アプリ開発ナレッジ

アプリ開発のナレッジを掲載します

マッチングアプリを個人開発する~Djangoバックエンドその5 決済機能実装~

この記事はマッチングアプリを自作してみようと試みている記事です
バックエンドを実装しています

前回までの記事はこちらです
shinseidaiki.hatenablog.com


前回はバックエンドのコア機能の動作確認を終えました
今回は積み残しのタスクとしての決済機能の実装を行っていきます
決済機能は不要だという方はこの章は飛ばしてもらってよいです

また、以下で紹介する実装は実運用には到底耐えないと思いますので本番運用を目指す方は更なるブラッシュアップを行ってください

目次

決済機能のStripeについての概要

今回決済機能にはStripeを利用します
Stripeとはクレジット決済機能代行サービスです
さらにクレジットのみならず Apple Pay や Google Play電子マネーの決済にもデフォルトで対応しており、豊富なドキュメントと実績があり、料金は決済時の手数料の3%のみというわかりやすさで、アプリケーション開発における決済代行機能プラットフォームのデファクトスタンダードといってもよいでしょう

ちなみに一昔前はJCBに非対応でなかなか大きな穴がありましたが今現在は対応されており、Stripeの使用にあたって突っ込みが入るところはほぼなくなっていると思います

なお初めのうちは決済機能の自作を考えるかもしれないですが、決済に関しては各種法令や業務要件・仕様が非常にシビアなため個人開発で決済機能を自作することは全く現実的ではないと思われます
素直に代行機能を利用する方が賢明だと思います

なお、今回マッチングアプリの実装で決済機能を導入する最大の目的は年齢確認のためですので、機能としては最もシンプルな実装にとどめたいと思います

アーキテクチャ設計

ではシステム構成とREST APIURIのエンドポイントとその機能をざっくり整理しておきます

システム構成図

やはり図を書いた方が何をこれからやるかがはっきりとするのでまずはお絵描きをします
図のような実装を行っていきます


画面のユーザー新規作成の段階では仮登録としてis_active=Falseであり、Emailに記載するURLからクレジット決済機能提供サービスのStripe経由でクレジット決済が完了したユーザーのみをアクティベーションします

ちなみにこれらの実装の目的としては不正なユーザーが簡単にユーザー登録をできないようにすることにあります
ただし残念ながらこの構成で目的が適うのかは私も知見がないのでわかりません
ここから先は多分大丈夫だろうという前提で実装を行っていきます

REST API設計

エンドポイント メソッド 機能内容
/api/users/{str:token_id}/payment/ GET Stripeが提供するクレジット決済決済画面にリダイレクトして、決済が成功した場合はユーザーアクティべーションのエンドポイントへリダイレクトし、キャンセルされた場合はキャンセルのエンドポイントにリダイレクトする。有効でないToken_idを指定したり、ユーザー新規作成からXX日以内にアクセスを行わない場合はStripeへのリダイレクトを行わず、決済画面へリダイレクトを行わない旨のメッセージを返す
/api/users/payment/cancel/ GET Stripeクレジット決済画面でキャンセルさせた場合にリダイレクトされてキャンセルされた旨のメッセージをレスポンスする
/api/users/{str:activate_token}/activation/ GET Stripeクレジット決済画面で決済が成功した後に呼び出されてユーザーのis_activeをtrueにすることでアクティベーションする。なおAPIとして公開されているためactivate_tokenは秘匿しておく必要がある


まとまりました
では実装をしていきましょう
ブログの説明の構成的には、アクティベーション機能とEmail機能とクレジット決済機能単位で分けた方が分かりやすいとは思いますが、コードがごちゃごちゃするので全機能をまとめてsettings.pyやmodel.pyから説明していきます
それぞれの機能の実装方法は今後切り分けたほうが分かりやすければブログにまとめたいとおもいます


各種機能を使用するための設定と環境変数を実装する

まずはEmailの機能とStripeを利用するための設定を行いましょう
Email機能はDjangoがデフォルトで用意しているものを使います

Stripeアカウント登録とStripeのインストール

Stripeアカウントを持っていない方はアカウントを作成してください
stripe.com

メール認証も済ませてアカウントをアクティベーションしてください
アカウントを作成するとダッシュボードが表示されていると思います
デフォルトでテスト環境になっていることを確認します
なお本番利用する場合はアプリサービスごとに都度申請が必要であるのでテスト環境だと思ってたら実は本番だった的なうっかり課金が起こってしまうことがないので初心者やちょっとAPIを試してみたいユーザーには心強い存在ですね

さて、アカウントを作成したらStripeパッケージをインストールします

pip install stripe

settings.pyの実装

次にDjangoでEmailとStripeを利用するための設定を行います

Email機能の設定

DjangoでEmailを利用したい場合はsettings.pyにEMAIL_BACKENDを設定すればよいことになっています
本番環境ではdjango.core.mail.backends.smtp.EmailBackendを使用しますが、テスト環境でメールの確認をしたい場合にはdjango.core.mail.backends.console.EmailBackendを用いることがコンソールに結果が出力されるのでお手軽に試したい場合はこちらを使うのが便利です
メールを実際に送信する場合はメールサーバーを用意したり、gmailを利用したりしなければならないので少し手間がかかるので基本的にはコンソール出力でテスト環境は対処します

settings.py

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # コンソールにメール内容を表示する
# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # メールを送信する

またメールには送り手の情報などが必要になるので必要項目をsettings.pyに記述します
settings.py

EMAIL_HOST = env.str('EMAIL_HOST')
EMAIL_PORT = env.int('EMAIL_PORT')
DEFAULT_FROM_EMAIL = env.str('DEFAULT_FROM_EMAIL')
EMAIL_HOST_USER = env.str('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = env.str('EMAIL_HOST_PASSWORD')
EMAIL_USE_TLS = env.bool('EMAIL_USE_TLS')

PASSWORDなどおよそコード上に残したくないものを設定するのでこれらの値は.envファイルに記述して環境変数化します
以下はgmailを使用する場合の設定です
EMAIL_HOST_PASSWORDはgmailの生パスワードか二段階認証を有効化した時に使えるアプリパスワードを設定します
私は横着して生パスワードで一度試しましたが、googleアカウントのセキュリティで弾かれたので、弾かれた場合はGoogleアカウントの設定(Chromeブラウザの設定ではないことに注意)>セキュリティ>安全性の低いアプリのアクセスを「オン」にすれば使用できるようなります
しかしGoogleサービスはスパム判定とかどうなってるのかよくわからず怖いのでこういったものを試すなら吹っ飛んでも大丈夫なGoogleアカウントを用意しておく方が無難な気がします

.env.dev or .env.prod

EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
DEFAULT_FROM_EMAIL=[YOUR.EMAIL]
EMAIL_HOST_USER=[YOUR.EMAIL]
EMAIL_HOST_PASSWORD=[YOUR.EMAIL.HOST.PASSWORD.BUT.APP.PASSWORD.FOR.GMAIL]
EMAIL_USE_TLS=True

決済機能の設定

次にStripeの設定を行います
こちらは特にSTRIPE_API_SECRET_KEYが大事です
MY_URLはStripeの成功・キャンセル時のリダイレクトに使うために使用します
STRIPE_ITEM_PRICEはマッチングアプリの開始利用料を設定します
これらも環境変数化します

settings.py

STRIPE_API_SECRET_KEY = env.str('STRIPE_API_SECRET_KEY')
MY_URL = env.str('MY_URL')
STRIPE_ITEM_PRICE = env.str('STRIPE_ITEM_PRICE')

.env.dev or .env.prod

STRIPE_API_SECRET_KEY=[STRIPE API シークレットキー]
MY_URL=http://127.0.0.1:8000
STRIPE_ITEM_PRICE=price_[STRIPE自動生成コード]

STRIPE_API_SECRET_KEYはダッシュボードの開発者向けのところで目隠しされているシークレットキーをコピーします
STRIPE_ITEM_PRICEに関してはドキュメントの販売商品の定義のところで自作した商品のコードを入力します
stripe.com

以下のようにテスト商品を作成することができるのでPRICE_IDに入った文字列をSTRIPE_ITEM_PRICEに格納する

Stripe例

line_items=[
  {
       # Provide the exact Price ID (for example, pr_1234) of the product you want to sell
        'price': '{{PRICE_ID}}', # ドキュメントで商品の値段を設定すると ------> 'price_[PRODUCT_KEY]'になるはず  
        'quantity': 1,
   },
],

設定はこれでおしまいです
次はモデルを作ります

Modelを実装する

次はモデルを実装します
仮登録ユーザーを本登録するアクティベーションの機能とメール送信機能を実装していきます
アクティベーションにはトークンを利用するのでトークンを格納するためのモデル作成していきます

アクティベーション機能の実装

現実世界で実際に運用されているWebサービスは通常、不正ユーザーの登録を防ぐために仮登録を行ってEmailの認証情報を入力させた後に本登録を完了させる実装が一般的かと思います
実装の具体的な方法はいろいろあるかと思いますが、今回は有効期限付きのトークンを発行して有効なトークンを持っているユーザーが期限内に決済を完了させた場合にアクティベーションをする実装とします

トークンを格納するためのモデルを作成する

まずは基本的なモデルを作ります
token_idは決済画面へリダイレクトするために用いてactivate_tokenは決済を通過した後のユーザーのアクティベーションのために用います
トークンとして用いるのは議論の余地がありそうですがトークンにはどちらもUUIDを使用しています
expired_atは有効期限です

models.py

class UserActivateTokens(models.Model):

    token_id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    activate_token = models.UUIDField(default=uuid.uuid4)
    expired_at = models.DateTimeField()

    objects = UserActivateTokensManager()

ここで UserActivateTokens のモデルはユーザーの時のようにマネージャークラスを使っているのでマネージャークラスを実装します

トークンとユーザーのデータを検証してアクティベーションをする機能の実装

マネージャークラスは公式ドキュメントでは「Django のモデルに対するデータベースクエリの操作を提供するインターフェイスです」と説明されていますが、分かりにくいのでモデルの特殊なメソッド置き場みたいなイメージでいいと思います
さて、マネージャーの中のactivate_user_by_tokenの処理がまさしくユーザーのアクティベーションを行っている処理です
user.is_active = Trueをしている箇所からわかると思います
また前処理としてアクティブにするユーザーは 正しいactivate_tokenと、かつ、有効期限が切れていないデータを持っているユーザーだけにフィルタリングしています
つまりactivate_token(※実際に渡してるのはtoken_id)をユーザーを新規作成した本人にしか分からない渡し方、つまりemailで渡すと、トークンはその人しか持っていないはずなので、ここでアクティベーションされるユーザーは、ユーザーを新規作成した本人のアカウントであるという認証を行うことができる仕掛けとなっています

models.py

class UserActivateTokensManager(models.Manager):

    def activate_user_by_token(self, activate_token):
        user_activate_token = self.filter(
            activate_token=activate_token,
            expired_at__gte=datetime.now() # __gte = greater than equal
        ).first()
        if hasattr(user_activate_token, 'user'):
            user = user_activate_token.user
            user.is_active = True
            user.save()
            return user
トークンを発行する処理

さて、ユーザーを作成した時に同時にトークンを発行してそのトークンをemailに乗せて送信出来たら便利ですが、Djangoにはちゃんとそのためのシグナルズ django.db.models.signals という機能が備わっています
シグナルズを利用すると何らかの処理がプログラム上で起こったときにタイミングをプログラム上で明示しなくても条件に合致した場合に自動的にあらかじめ定義したメソッドを実行してくれる機能です
例えば今回利用するようなユーザーが作成された場合に自動的にトークンを発行してメール送信するといったような実
装です
シグナルズの使い方ですが、アノテーションを利用することで使えます
アノテーションとはメソッドの手前に@がつくものです
例えば、今回使う@receiver(post_save, )のようなものです
ちなみにシグナルズと混乱するかもですが、アノテーションをつけていると今回のようなメソッドの実行タイミングの制御やパーミッションの制御などに用いることができます
コード量を減らす工夫です
そして、@receiver(post_save, sender)ですがこれをつけるとsenderに定義したモデルが保存の処理を行うたびに直下のメソッドが自動的に呼び出されます
ここではユーザーが新規作成されたりフィールドが更新されて保存されたタイミングで呼び出されています
シグナリングを使用しなくてもviews.pyに処理をゴリゴリ書いてもいいですが、シグナリングを使用する方が見通しが良くなる場合はこちらで記述するのが得策です

そして、以下がトークンを発行する処理になります
userにはシグナルズでuser.save()メソッドを呼んだつまり新規作成されたユーザーのインスタンスが入ってきます
expired_atの有効期限は現在時刻にsettings.ACTIVATION_EXPIRED_DAYSで定義した3日を足した時刻を格納しています
アクティベーションのメソッドを呼び出すのはこの日時までだよという意味にあたるのがexpired_atになります

models.py

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def publish_activate_token(sender, instance, **kwargs):
    if not instance.is_active:
        user_activate_token = UserActivateTokens.objects.create(
            user=instance,
            expired_at=datetime.now()+timedelta(days=settings.ACTIVATION_EXPIRED_DAYS),
        )
    # .........Emailを送信する処理   

またexpired_atの日付の格納に関してもAwareやNaiveなどのややこしい話があってこの実装ではDjangoのコンソールに警告文が出てしまうのですが、今回は気にしないことにします
この点に関しては記事を少しまとめたものがあるので暇があれば読んでください
qiita.com

トークンをEmailで送信する処理の実装

Emailで送信する処理ですが、Djangoはsend_mail()メソッドを呼ぶだけでメールを送ることができます
引数の意味はぱっと見でわかると思いますので以下実装で示します
上のpublish_activate_token()メソッドの続きにEmail処理を記述して以下のようにします
メール本文に{settings.MY_URL}/api/users/{user_activate_token.token_id}/payment/を記述しており、これをクリックするとStripeの決済画面に遷移する動きになっています
またpost_saveはマネージャークラスでis_activeをtrueにするときにも反応するのでアクティベーション完了のメールも送信する設定にしました

models.py

from django.core.mail import send_mail

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def publish_activate_token(sender, instance, **kwargs):
    if not instance.is_active:
        user_activate_token = UserActivateTokens.objects.create(
            user=instance,
            expired_at=datetime.now()+timedelta(days=settings.ACTIVATION_EXPIRED_DAYS),
        )
        subject = 'Please Activate Your Account'
        message = f'URLにアクセスして決済を完了してください。\n {settings.MY_URL}/api/users/{user_activate_token.token_id}/payment/'
    if instance.is_active:
        subject = 'Activated! Your Account!'
        message = 'ユーザーが使用できるようになりました'
    from_email = settings.DEFAULT_FROM_EMAIL
    recipient_list = [
        instance.email,
    ]
    send_mail(subject, message, from_email, recipient_list)  

ルーティングURLの実装をする

Viewの前にルーティング設定であるurls.pyの実装を行っておきます
アーキテクチャ設計のところで記載したものと同じです

urls.py

from .views import activate_user
from .views import pay_stripe
from .views import pay_stripe_cancel 

urlpatterns = [
    path('users/<uuid:token_id>/payment/', pay_stripe, name='pay-stripe'),  # Stripe決済画面にリダイレクト
    path('users/payment/cancel/', pay_stripe_cancel, name='pay-stripe-cancel'),  # 決済失敗時に実行
    path('users/<uuid:activate_token>/activation/', activate_user, name='users-activation'),  # 決済成功時にユーザーアクティベーションを実行
]

Viewを実装する

Viewの実装では決済機能とユーザーアクティベーションの処理を実装します

決済機能の実装

Stripeを利用するための実装

今回のAPI機能はDjangoで作成するモデルをメインで使うよりはStripeの決済機能を使うロジックの方が多いので、メソッドベースのAPIViewを採用します
そして以下のアノテーションをつけてアクセス制御を行っています
@api_view(['GET'])
@permission_classes([AllowAny])

また、処理に関してはほぼStripeのドキュメントをそのまま利用しています
success_urlとcancel_urlはぱっと見でわかると思いますが成功時とキャンセル時のリダイレクト先を記載するところです
こちらのアプリで想定するURLに飛ばすように設定します
success_urlにはアクティベーション用のトークンをくっつけておきます

views.py

from rest_framework.decorators import api_view, permission_classes
from django.shortcuts import redirect
from django.conf import settings
import stripe

stripe.api_key = settings.STRIPE_API_SECRET_KEY


@api_view(['GET'])
@permission_classes([AllowAny])
def pay_stripe(request, token_id):
    try:
        # ......tokensの前処理
        checkout_session = stripe.checkout.Session.create(
            line_items=[
                {
                    'price': settings.STRIPE_ITEM_PRICE,
                    'quantity': 1,
                },
            ],
            mode='payment',
            success_url=f'{settings.MY_URL}/api/users/{tokens.activate_token}/activation/',
            cancel_url=f'{settings.MY_URL}/api/users/payment/cancel/',
        )
    except Exception as e:
        return str(e)
    return redirect(checkout_session.url, code=303)
token_idを受け取ってアクティベーションさせるメソッドにはactivate_tokenを渡す処理の実装

今回token_idとactivate_tokenでトークンを分けていましたが、これはtoken_idをもしusers/{str:token_id}/activation/のようにアクティベーションURIに使ったとしたらユーザーがURLのエンドポイントの/activation/を推測できると決済を通さずにアクティベーションできてしまうというリスクがあるからでした
なので、token_idを決済画面のユーザー識別に使ってactivate_tokenをアクティベーショントークンとして使い分ける実装まで行ったStripe決済画面呼び出しメソッド pay_stripe(request, token_id) は以下のようになります
といってもマネージャークラスで実装した処理とほとんど一緒です
これでトークンが流出するかUUIDが推測でもされない限り決済をスキップされる可能性がなくなりました

views.py

from .models import UserActivateTokens

@api_view(['GET'])
@permission_classes([AllowAny])
def pay_stripe(request, token_id):
    try:
        tokens = UserActivateTokens.objects.all().filter(
            token_id=token_id,
            expired_at__gte=datetime.now()
        ).first()
        if tokens is None:
            return Response({'message': 'トークン間違いもしくはトークンの有効期限切れです'})
        checkout_session = stripe.checkout.Session.create(
            line_items=[
                {
                    'price': settings.STRIPE_ITEM_PRICE,
                    'quantity': 1,
                },
            ],
            mode='payment',
            success_url=f'{settings.MY_URL}/api/users/{tokens.activate_token}/activation/',
            cancel_url=f'{settings.MY_URL}/api/users/payment/cancel/',
        )
    except Exception as e:
        return str(e)
    return redirect(checkout_session.url, code=303)
Stripeで決済をキャンセルしたときの処理の実装

キャンセルされたときの処理です
メッセージを返すだけです
Djangoで画面も作る実装の場合はHttpResponseを返しますが、今回のようにバックエンド特化のREST APIの場合はResponseを返すという違いがあります

views.py

from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response

@api_view(['GET'])
@permission_classes([AllowAny])
def pay_stripe_cancel(request):
    return Response({'message': '決済がキャンセルされました'})
ユーザーアクティベーションの処理の実装

決済が成功した場合はユーザーのアクティベーションを行うメソッドがリダイレクトされて呼び出されます
UserActivateTokens.objects.activate_user_by_token(activate_token)がアクティベーションの処理を呼び出しています
大まかには決済が成功して想定通りユーザーがアクティベーションされればactivated_userが返ってきて、何らかの障害やエラーが発生した場合には失敗のメッセージを送る想定としています

ただしこのメソッドは決済が絡んでくるためサービスの品質として絶対に落としてはいけないものであるので、本番環境ではこの処理の失敗時に特別なログを出力させて管理者に通知する実装をとった方が良いと思われます
余力があればそちらの実装も行っていきたいと思います

views.py

from .models import UserActivateTokens
from rest_framework.decorators import api_view, permission_classes


@api_view(['GET'])
@permission_classes([AllowAny])
def activate_user(request, activate_token):
    activated_user = UserActivateTokens.objects.activate_user_by_token(activate_token)
    if hasattr(activated_user, 'is_active'):
        if activated_user.is_active:
            message = {'message': 'ユーザーのアクティベーションが完了しました'}
        if not activated_user.is_active:
            message = {'message': 'アクティベーションが失敗しています。管理者に問い合わせてください'}
    if not hasattr(activated_user, 'is_active'):
        message = {'message': 'エラーが発生しました'}
    return Response(message)

このメソッドではGETメソッドを使っていますが、Userモデルの変更を伴っているのにGETで呼び出していいのかなと思いつついい方法が思い浮かばなかったのでこの実装にしています

動作確認

さて簡単に動作を確認していきます
まずはDBの変更を伴っているのでマイグレーションを行ってから起動しましょう

py manage.py makemigrations
py manage.py migrate
py manage.py runserver

ユーザー作成時のトークン生成とEmail通知の確認

POSTMANでhttp://127.0.0.1:8000/api/users/create/にアクセスして、アカウントを作成しましょう
するとコンソールに次のようなEmailが返ってくることが確認できると思います

Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Subject: Please Activate Your Account
From: super@user.com
To: user5@user.com
Date: Sat, 19 Feb 2022 11:27:48 -0000
Message-ID: 
 <***********************************************>

URLにアクセスして決済を完了してください。
 http://127.0.0.1:8000/api/users/[token-id-uuid]/payment/
-------------------------------------------------------------------------------
[19/Feb/2022 20:27:48] "POST /api/users/create/ HTTP/1.1" 201 84

また管理画面でもユーザーが作成できていて、is_activeがFalseになっていることを確認します
またトークンが発行されていることも見ておきましょう
トークンテーブルを見るのはadmin.pyをいじって管理画面にテーブルを増やすか、CB Browswe for SQLiteのようなUIで操作が出来るフリーのツールをインストールしてきて、プロジェクトフォルダのdb.sqlite3を開けば確認することができます
トークンも同時に生成されていることが分かりました

Stripe決済機能とアクティベーション機能の確認

さて、emailの本文にあるhttp://127.0.0.1:8000/api/users/[token-id-uuid]/payment/にアクセスしましょう
Stripeの画面にリダイレクトしていることを確認します

さてテスト用の番号は以下のようになっているので試してみましょう

支払いが成功しました 4242 4242 4242 4242
支払いには認証が必要です 4000 0025 0000 3155
支払いが拒否されました 4000 0000 0000 9995

stripe.com


4242 4242 4242 4242を入力してその他の項目も適当に埋めてOKを押します

チェックがかかりいつものDjangoのブラウザのRest API実行環境にリダイレクトされます
アクティベーションが成功した旨のメッセージが出ていれば成功です

{
    "message": "ユーザーのアクティベーションが完了しました"
}

逆にOKボタンを押さずに戻るボタンを押すなどするとキャンセルが起こります
キャンセルのリダイレクトも確認しておきましょう

Stripeを使えない場合のトラブルシュート

以下のようなエラーが発生した場合は、Stripeのアカウント名を変更する必要がある

InvalidRequestError at /pay/checkout/
In order to use Checkout, you must set an account or business name at https://dashboard.stripe.com/account.

ダッシュボードにアクセスし、プロフィール>アカウントのところで「名称未設定」のアカウントと表示されていればアカウント名を登録する
登録方法は 設定(歯車アイコン)>アカウントの詳細>アカウント名 で好きなアカウント名を入力する
保存を押すことでアカウント名は保存される
これで再度実行するとできるはずです

決済を通さないで不正にアクティベーションの実行を試みた場合に失敗するかを確認する

存在しないもしくは適当に自身で生成したuuidをactivate-token-uuidの部分に代入してアクティベーションを試みます
http://127.0.0.1:8000/api/users/[発行されていないactivate-token-uuid]/activation/

{
    "message": "エラーが発生しました"
}

失敗のメッセージが返ってきて、どのユーザーも勝手にアクティベーションされていないことを確認しておきます


これで、動作確認は終了しました
お疲れ様です


次回予告

これで実装したいバックエンドの機能は完成しました
次は変更に備えてテストと行きたいところですが、まずはフロントエンドの画面を先に作ろうと思います
テストは少し後回しにしたいと思います


次回記事

shinseidaiki.hatenablog.com