import CliVersionRequirements from 'app/views/partials/payments/cli-version-requirements.mdx'
<Repo framework="remix" href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix" />
<Picker name="framework">
<PickerOption name="remix" />
</Picker>
<Overview>
<Notice type="beta">
Processing a payment with a credit card payments extension is supported as of API version 2023-04 and is currently in an invite-only closed beta.
</Notice>
<CliVersionRequirements />
Credit card payments extensions allow customers to complete the payment process directly on the merchant's website, with additional features such as 3DS authentication.
When a store enables your credit card payments extension, credit card payments are processed by your extension, directly in Shopify checkout. When a customer then enters their credit card details into checkout, Shopify sends the payment details to your payments extension, which processes the payment and returns the result to Shopify, while the customer remains on the storefront.
To make a production-ready extension, you need to make several changes to the template code to include your own payment processing capabilities. This tutorial highlights the areas you need to edit or extend with your own functionality.
## What you'll learn
In this tutorial, you'll learn how to do the following tasks:
- Set up your app using the [Remix template for credit card payments apps](https://github.com/Shopify/example-app--credit-card-payments-app-template--remix)
- Create a credit card payments extension
- Explore the payment, refund, void, reject and capture session flows, and how to implement them yourself
- Configure 3-D Secure authentication
- Submit your payments extension for review
</Overview>
<Requirements>
<Requirement href="https://www.shopify.com/partners" label="Create a Partner account" />
<Requirement href="/docs/apps/tools/development-stores" label="Create a development store">
The development store should be pre-populated with test data.
</Requirement>
<Requirement href="/docs/apps/build/payments/payments-extension-review#payments-partner-application-review" label="Become a Payments Partner">
Apply and receive approval to become a Payments Partner.
</Requirement>
<Requirement href="/docs/apps/payments/credit-card/manage-encryption-certificates" label="Create an encryption certificate">
An encryption certificate is required to decrypt credit card information in your PCI DSS compliant environment.
</Requirement>
</Requirements>
<StepSection>
<Step>
## Create a payments app
Create a new payments app using [Shopify CLI](/docs/api/shopify-cli).
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/extensions-templates/blob/main/payments-app-extension-credit-card/shopify.extension.toml.liquid"
/>
### Scaffold an app using Shopify CLI
1. Run the following command to start creating your app:
<Codeblock terminal>
```bash title="npm"
npm init @shopify/app@latest
```
</Codeblock>
1. When prompted, enter the name of your app.
1. When prompted for the approach, select the following option to add your first extension:
<Codeblock terminal>
```bash title="npm"
Start with Remix(recommended)
> Start by adding your first extension
```
</Codeblock>
</Substep>
</Step>
<Step>
## Create a payments extension
Your Shopify app becomes a payments app after you've created and configured your payments extension.
<Substep>
1. Run the following command to start generating your payment extension:
<Codeblock terminal>
```bash title="pnpm"
pnpm shopify app generate extension
```
</Codeblock>
1. When prompted, choose your organization and create a new app.
1. When prompted for **Type of extension**, select **Payments App Extension > Credit Card** and name your extension.
</Substep>
</Step>
<Step>
## Configure your payments extension
When you [generate an extension](/docs/api/shopify-cli/app/app-generate-extension), a TOML configuration file named `shopify.extension.toml` is automatically generated in your app's extension directory. You can find your extension configuration in `extensions/<extension-name>/shopify.extensions.toml`.
<Substep>
| Property name | Description |
| ------------- | ---------------------------------------------------------------------------------- |
| `payment_session_url` <br /><span class="heading-flag">Required</span> | The URL that receives payment and order details from the checkout. |
| `refund_session_url` <br /><span class="heading-flag">Required</span> | The URL that refund session requests are sent to. |
| `capture_session_url` <br /><span class="heading-flag">Required</span> | The URL that capture session requests are sent to. |
| `void_session_url` <br /><span class="heading-flag">Required</span> | The URL that void session requests are sent to. |
| `confirm_session_url` <br /><span class="heading-flag">Optional</span> | The URL that confirm session requests are sent to. This URL is required if your payments app supports 3-D Secure authentication. |
| `supported_countries` <br /><span class="heading-flag">Required</span> | The countries where your payments app is available. List of ISO 3166 (alpha-2) country codes your app is available for installation by merchants. Learn more: https://www.iso.org/iso-3166-country-codes.html |
| `supports_3ds` <br /><span class="heading-flag">Required</span> | 3-D Secure support is mandated in some instances. For example, you must enable the 3-D Secure field if you plan to support payments in countries which have mandated 3-D Secure. |
| `supported_payment_methods` <br /><span class="heading-flag">Required</span> | The payment methods (for example, Visa) that are available with your payments app. Learn more: https://github.com/activemerchant/payment_icons/blob/master/db/payment_icons.yml |
| `supports_installments` <br /><span class="heading-flag">Required</span> | Enables installments |
| `supports_deferred_payments` <br /><span class="heading-flag">Required</span> | Enables deferred payments |
| `merchant_label` <br /><span class="heading-flag">Optional</span> | The name for your payment provider extension. This name is displayed to merchants in the Shopify admin when they search for payment methods to add to their store. Limited to 50 characters. |
| `test_mode_available` <br /><span class="heading-flag">Optional</span> | Enables merchants using your payments app to test their setup by simulating transactions. To test your app on a development store, your payment provider in the Shopify admin must be set to test mode. |
| `api_version` <br /><span class="heading-flag">Optional</span> | The Payments Apps GraphQL API version used by the payment provider app to receive requests from Shopify. You must use the same API version for sending GraphQL requests. You can't use the unstable version of the API in production. API versions are updated in accordance with Shopify's general API versioning timelines. |
| `multiple_capture` <br /><span class="heading-flag">Optional, Closed Beta</span> | Enables merchants using your payment provider app to partially capture an authorized payment multiple times up to the full authorization amount. This is used only if your payments app supports merchant manual capture. |
| `encryption_certificate_fingerprint` <br /><span class="heading-flag">Required</span> | The certificate that Shopify uses to generate the ephemeral key and encrypt the credit card information of the customer. Refer to [manage encryption certificates](/docs/apps/build/payments/credit-card/manage-encryption-certificates) section to learn more. |
</Substep>
</Step>
<Step>
## Set up your payments app
<Substep>
### Disable embedding
Shopify apps are embedded by default, but payments apps are an exception to this, because they don't need to render anything in Shopify admin.
In `shopify.app.toml`, update the `embedded` and set it to `false`.
</Substep>
<Substep>
### Configure basic app settings
In `shopify.app.toml`, update the `name` and `client_id` to match the information about the app that you manually created. You can find the `client_id` in the **Client credentials** section of your app's overview page in the [Partner Dashboard](https://partners.shopify.com/apps/).
</Substep>
<Substep>
### Start your development server
To run the app locally, start your development server:
1. Install the packages required to run the payments app:
<Codeblock terminal>
```bash title="npm"
npm install
```
```bash title="Yarn"
yarn install
```
```bash title="pnpm"
pnpm install
```
</Codeblock>
1. Run the following command:
<Codeblock terminal>
```bash
shopify app dev
```
</Codeblock>
<Notice type="info">
You might be prompted to log in to your Partner account.
</Notice>
In your terminal, select your development store. You can use the generated URL to test your payments app by using it in your [payments app configuration](#configure-your-payments-extension). If you want a consistent tunnel URL, then you can use the `--tunnel-url` flag with your own tunnel when starting your server.
1. Press `p` to open the app in your browser. This brings you to your development store's admin, where you can install your payments app.
</Substep>
<Substep>
### Push the configuration changes to your app and start your server
In a terminal, run the following commands to push the configuration changes to your app:
1. Deploy your app to update the config, which is defined in `shopify.app.toml`:
<Codeblock terminal>
```bash
shopify app deploy
```
</Codeblock>
</Substep>
</Step>
<Step>
## Explore payment sessions
In this step, you'll explore the flows that an app needs to implement to process a payment.
In the app template, the endpoint that handles start payment session requests is predefined, and will automatically resolve or reject the payment by calling the Payments Apps API, based on the customer's name.
<Notice type="note">
The behavior of the endpoint that handles start payment session requests is exclusively for testing and should be replaced with your own payment processing logic in a production app.
</Notice>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/routes/app.payment_session.jsx"
tag="build-credit-card-payments-app.payment-session"
/>
### Start the payment session
When a customer selects your payment provider, Shopify sends an HTTP `POST` request the payment session URL for the app. The request contains information about the customer and the order.
When the `POST` request is received, the payments app returns an HTTP `2xx` response. This response is required for payment session creation to be considered successful.
If the request fails, then it's retried several times. If the request still fails, then the customer needs to retry their payment through Shopify checkout. If there's an error on the payments app's side, then return an appropriate error status code instead.
---
You configure the payment session URL for your app as part of the [app extension configuration](#configure-your-payments-extension).
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/encryption.js"
tag="build-credit-card-payments-app.credit-card-decryption"
/>
### Decrypt the credit card information
The credit card information of the customer is encrypted using the ECIES hybrid encryption scheme, and passed during payment session creation as `encrypted_message` under `payment_method.data`. Credit card information is decrypted using the following steps:
1. Decode the Base64 encoded `encrypted_message` and `tag`.
1. Use `fingerprint` to identify the certificate that's used for encryption.
1. Compute the shared secret using the private key of the certificate and `ephemeral_public_key`.
1. Compute the hmac of the decoded `encrypted_message` and ensure it matches the decoded value of `tag`.
1. Decrypt the `encrypted_message`.
<Notice type="warning" title="Caution">
The application or service that decrypts the value of the `encrypted_message` must be in a PCI DSS compliant environment.
</Notice>
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/payments-apps.graphql.js"
tag="build-credit-card-payments-app.graphql.resolve"
/>
### Resolve a payment
Payments apps use the `paymentSessionResolve` mutation after the customer has successfully gone through the payment process to complete the payment. The `id` argument corresponds to the global identifier (`gid`) of the payment.
The `authorizationExpiresAt` argument must be provided if the payment session request `kind` is `authorization`. The `PaymentSessionThreeDSecureAuthentication` argument must be provided if the `paymentSessionRedirect` mutation has been called for [3-D Secure](#supporting-3-d-secure).
---
In the referenced code, `this.resolveMutation` corresponds to the `paymentSessionResolve` mutation.
<Resources>
[`paymentSessionResolve`](/docs/api/payments-apps/latest/mutations/paymentSessionResolve)
[`PaymentSessionThreeDSecureAuthentication`](/docs/api/payments-apps/latest/input-objects/PaymentSessionThreeDSecureAuthentication)
</Resources>
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/payments-apps.graphql.js"
tag="build-credit-card-payments-app.graphql.reject"
/>
### Reject a payment
The payments app should reject a payment if the customer can't complete a payment with the provider. The rejected payment tells Shopify that the checkout process will be halted. For example, if you don't want to process a high-risk payment, then you can reject the payment using the `paymentSessionReject` mutation.
---
In the referenced code, `this.rejectMutation` corresponds to the `paymentSessionReject` mutation.
<Resources>
[`paymentSessionReject`](/docs/api/payments-apps/latest/mutations/paymentSessionReject)
</Resources>
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/routes/app.payment_session.jsx"
tag="build-credit-card-payments-app.payment-session.process-payment"
/>
### Triggering resolve, or reject a payment in the template
In the app template, after a start payment session request has been received by the app, the payment is automatically resolved or rejected based on the customer's name.
If the customer's first name is `reject`, then the payment is rejected. Otherwise, the payment is resolved.
</Substep>
<Substep>
### Timeout
Credit card payment apps are expected to process payments in a few seconds. To ensure a good customer experience, the customer is redirected to the order confirmation page when a payment times out. However, the order status is automatically set to **Pending** in the Shopify admin. You should still finalize the payment by calling the `paymentSessionResolve` or `paymentSessionReject` mutations.
</Substep>
</Step>
<Step>
## Supporting 3-D Secure
If you add support for 3-D Secure to your payments app, then it requires some additional configuration and logic prior to resolving or rejecting the payment.
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/payments-apps.graphql.js"
tag="build-credit-card-payments-app.graphql.redirect"
/>
### Redirect the customer
After the payments app determines that it needs to perform a 3-D Secure (3DS) authentication, and after the app returns a response to HTTP `POST` request sent from Shopify to [initiate the payment flow](#start-the-payment-session), the app should use the `paymentSessionRedirect` mutation to provide to the URL where 3DS authentication will take place. Shopify renders the URL provided by the app in an `iframe` in the customer's browser.
The `paymentSessionRedirect` mutation request must be received by Shopify [within a given timeout](#timeout). After the timeout, Shopify redirects the customer back to Shopify's checkout, and will fail any additional `paymentSessionRedirect` mutations with the `BUYER_ALREADY_REDIRECTED_BY_SHOPIFY` error code.
The `id` argument corresponds to the global identifier (`gid`) of the payment.
---
To defend against clickjacking, Shopify sets the `source` of the `frame-ancestors` directive in the `Content-Security-Policy` header to `'none'`. This prevents any domain from framing the page containing the `iframe` where Shopify renders the URL provided by the app.
In the referenced code, `this.redirectMutation` corresponds to the `paymentSessionRedirect` mutation.
<Resources>
[`paymentSessionRedirect`](/docs/api/payments-apps/latest/mutations/paymentSessionRedirect)
</Resources>
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/routes/app.payment_session.jsx"
tag="build-credit-card-payments-app.payment-session.3ds-redirect-url"
/>
### Triggering 3-D Secure in the template
In the app template, 3-D Secure is initiated when the customer's first name is `3ds`. The app template redirects the customer to the 3-D Secure simulator.
In a production app, you would redirect the customer to your own 3-D Secure authentication page.
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/payments-apps.graphql.js"
tag="build-credit-card-payments-app.graphql.confirm"
/>
### Confirm a payment
You must use the `paymentSessionConfirm` mutation to confirm with Shopify whether to proceed with the payment request, according to Shopify's business logic. For example, Shopify checks that products aren't oversold and that discount codes are still valid. If you perform a 3-D Secure challenge, then this mutation should be run after the challenge is performed.
The `id` argument corresponds to the global identifier (`gid`) of the payment.
---
In the referenced code, `this.confirmMutation` corresponds to the `paymentSessionConfirm` mutation.
<Resources>
[`paymentSessionConfirm`](/docs/api/payments-apps/latest/mutations/paymentSessionConfirm)
</Resources>
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/routes/app.three-d-secure.%24paymentId.jsx"
tag="build-credit-card-payments-app.next-action"
/>
### Triggering a payment confirmation in the template
In the app template, the 3-D Secure simulator confirms the payment after the user chooses whether to complete the challenge successfully or with an error.
In a production app, you would confirm the payment after the customer completes the 3-D Secure challenge, based on the result of the challenge.
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/routes/app.confirm_session.jsx"
tag="build-credit-card-payments-app.confirm-session"
/>
### Process a confirm session
Following `paymentSessionConfirm`, when Shopify determines that the payment request can proceed based on its business logic, Shopify sends a `POST` request to the confirm session URL of the credit card payments app extension, delivering the confirmation result.
Shopify must receive an HTTP `200` or `201` response for the payment session confirmation to be successful.
If the request fails, then it's retried several times. If the request still fails, then the customer needs to retry their payment through Shopify checkout.
If there's an error on the payments app's side, then don't respond with an HTTP `2xx`. Use an appropriate error status code instead.
---
At this point, your app can [begin processing the payment](#explore-payment-sessions), and can call either of `paymentSessionResolve` or `paymentSessionReject` to resolve or reject the payment respectively.
When Shopify indicates that the payment request can't proceed, the payments app must invoke the `paymentSessionReject` mutation using the `CONFIRMATION_REJECTED` reason code.
The app template is set up to store and automatically resolve or reject a payment session based on either the 3-D Secure challenge's result or the customer's last name when a confirm request is sent from Shopify to the app. Your app should replace this logic with its own post-confirmation processing logic.
<Resources>
[`paymentSessionResolve`](/docs/api/payments-apps/latest/mutations/paymentSessionResolve)
[`paymentSessionReject`](/docs/api/payments-apps/latest/mutations/paymentSessionReject)
[`CONFIRMATION_REJECTED`](/docs/api/payments-apps/latest/enums/PaymentSessionStateRejectedReason#value-confirmationrejected)
</Resources>
</Substep>
</Step>
<Step>
## Explore refund sessions
In this step, you'll explore the flows that an app needs to implement to process a refund.
In the app template, the endpoint that handles start refund session requests is predefined to store sessions for an asynchronous resolution.
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/routes/app.refund_session.jsx"
tag="build-credit-card-payments-app.refund-session"
/>
### Start the refund session
The refund flow begins with an HTTP `POST` request sent from Shopify to the payments app's refund session URL. Shopify must receive an HTTP `201` (Created) response for the refund session creation to be successful.
If the request fails, then it's retried several times. If the request still fails, then the user needs to manually retry the refund in the Shopify admin.
---
You configure the refund session URL for your app as part of the [app extension configuration](#configure-your-payments-app-extension).
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/payments-apps.graphql.js"
tag="build-credit-card-payments-app.graphql.resolve"
/>
### Resolve a refund
After the app has successfully processed the refund request, it's resolved by using the `refundSessionResolve` mutation. The `id` argument corresponds to the `gid` of the refund.
---
In the referenced code, `this.resolveMutation` corresponds to the `refundSessionResolve` mutation.
<Resources>
[`refundSessionResolve`](/docs/api/payments-apps/latest/mutations/refundSessionResolve)
</Resources>
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/payments-apps.graphql.js"
tag="build-credit-card-payments-app.graphql.reject"
/>
### Reject a refund
If the app can't process a refund, then it needs to reject it. You should only reject a refund in the case of final and irrecoverable errors. Otherwise, you can attempt to process the refund again.
The refund is rejected using the `refundSessionReject` mutation.
As part of the rejection, a reason why the refund was rejected must be included as part of `RefundSessionRejectionReasonInput`.
The `RefundSessionRejectionReasonInput.code` is a `RefundSessionStatusReasonRejectionCode`, which is an enum of standardized error codes.
The `RefundSessionRejectionReasonInput.merchantMessage` argument is a localized error message presented to the merchant explaining why the refund was rejected.
---
In the referenced code, `this.rejectMutation` corresponds to the `refundSessionReject` mutation.
<Resources>
[`refundSessionReject`](/docs/api/payments-apps/latest/mutations/refundSessionReject)
[`RefundSessionRejectionReasonInput`](/docs/api/payments-apps/latest/input-objects/RefundSessionRejectionReasonInput)
[`RefundSessionStatusReasonRejectionCode`](/docs/api/payments-apps/latest/enums/RefundSessionStatusReasonRejectionCode)
</Resources>
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/routes/app.dashboard_simulator.%24paymentId.jsx"
tag="build-credit-card-payments-app.dashboard-simulator.process-session"
/>
### Triggering resolve or reject for a refund in the template
In the app template, the simulator built into the dashboard handles the resolution or rejection of all post-payment sessions, including refunds, asynchronously. This means that after a refund is created in a store's Shopify admin, it must be manually completed from the app template's dashboard.
In a production-ready app, your app would process the refund itself once it receives the start refund session request.
</Substep>
</Step>
<Step>
## Explore capture sessions
A capture describes the process of how merchants capture funds for an authorized payment. A capture is the next step of the payment flow, and occurs after an authorized payment is finalized. Finalized payments have `kind` set to `authorization`.
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/routes/app.capture_session.jsx"
tag="build-credit-card-payments-app.capture-session"
/>
### Start the capture session
A capture can only be performed when the payment initiated by Shopify has a `kind` property with a value of `authorization`. With an `authorization`, the app places a hold on funds and then replies to Shopify's capture request.
The capture flow begins with an HTTP POST request sent from Shopify to the payments app's capture session URL.
---
You configure the capture session URL for your app as part of the [app extension configuration](#configure-your-payments-app-extension).
Shopify sends a capture request to the payments app after a merchant tries to capture the funds on an authorized transaction. When this occurs, the app template is set up to store a capture session. These sessions can then be resolved through the simulator for testing. In a production-ready app, this is when your app would process the capture request.
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/payments-apps.graphql.js"
tag="build-credit-card-payments-app.graphql.resolve"
/>
### Resolve a capture session
After the app has successfully processed the capture request from Shopify, it's resolved using the `captureSessionResolve` mutation.
---
In the referenced code, `this.resolveMutation` corresponds to the `captureSessionResolve` mutation.
<Resources>
[`captureSessionResolve`](/docs/api/payments-apps/latest/mutations/captureSessionResolve)
</Resources>
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/payments-apps.graphql.js"
tag="build-credit-card-payments-app.graphql.reject"
/>
### Reject a capture
If you don't want to process a capture request, then you should reject it. You might want to reject a capture if authorization has expired or if you suspect that the request is fraudulent or high risk. You should only reject a capture in the case of final and irrecoverable errors. Otherwise, you should re-attempt to resolve the capture.
The app rejects a capture using the `captureSessionReject` mutation.
As part of the rejection, you need to include a reason why the capture was rejected as part of `CaptureSessionRejectionReasonInput`.
The `CaptureSessionRejectionReasonInput.code` is a `CaptureSessionStatusReasonRejectionCode`, which is an enum of standardized error codes.
The `CaptureSessionRejectionReasonInput.merchantMessage` argument is a localized error message presented to the merchant explaining why the capture was rejected.
---
In the referenced code, `this.rejectMutation` corresponds to the `captureSessionReject` mutation.
<Resources>
[`captureSessionReject`](/docs/api/payments-apps/latest/mutations/captureSessionReject)
[`CaptureSessionRejectionReasonInput`](/docs/api/payments-apps/latest/input-objects/CaptureSessionRejectionReasonInput)
[`CaptureSessionStatusReasonRejectionCode`](/docs/api/payments-apps/latest/enums/CaptureSessionStatusReasonRejectionCode)
</Resources>
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/routes/app.dashboard_simulator.%24paymentId.jsx"
tag="build-credit-card-payments-app.dashboard-simulator.process-session"
/>
### Triggering resolve or reject for a capture in the template
In the app template, the simulator built into the dashboard (`/app/dashboard`) handles the resolution or rejection of all post-payment sessions, including captures, asynchronously. This means that after a capture is created in a store's Shopify admin, it must be manually completed from the app template's dashboard.
In a production-ready app, your app would process the capture itself after it receives the start capture session request.
</Substep>
</Step>
<Step>
## Explore void sessions
A void describes the process of how merchants void funds for an authorized payment. A void is the next step of the payment flow, and occurs after an authorized payment is finalized. Finalized payments have `kind` set to `authorization`.
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/routes/app.void_session.jsx"
tag="build-credit-card-payments-app.void-session"
/>
### Start the void session
A void can only be performed when the payment initiated by Shopify has a `kind` property with a value of `authorization`.
The void flow begins with an HTTP `POST` request sent from Shopify to the payments app's void session URL.
---
You configure the void session URL for your app as part of the [app extension configuration](#configure-your-payments-app-extension).
The app template stores the void session when Shopify sends a void request to a payments app after a merchant tries to cancel the order for an authorized transaction. These sessions can then be resolved through the simulator for testing. In a production-ready app, this is when your app would process the void request.
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/payments-apps.schema.js"
tag="build-credit-card-payments-app.schema.void-resolve"
/>
### Resolve a void session
After the app has successfully processed the void request, it is resolved using the `voidSessionResolve` mutation.
---
<Resources>
[`voidSessionResolve`](/docs/api/payments-apps/latest/mutations/voidSessionResolve)
</Resources>
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/payments-apps.schema.js"
tag="build-credit-card-payments-app.schema.void-reject"
/>
### Reject a void
If your app can't process a void request, then you should reject it. You should only reject a void in the case of final and irrecoverable errors. Otherwise, you can attempt to resolve the void again.
You can reject a void using the `voidSessionReject` mutation.
As part of the rejection, you need to include a reason why the void was rejected as part of `VoidSessionRejectionReasonInput`.
The `VoidSessionRejectionReasonInput.code` is a `VoidSessionStatusReasonRejectionCode`, which is an enum of standardized error codes.
The `VoidSessionRejectionReasonInput.merchantMessage` argument is a localized error message presented to the merchant explaining why the void was rejected.
---
<Resources>
[`voidSessionReject`](/docs/api/payments-apps/latest/mutations/voidSessionReject)
[`VoidSessionRejectionReasonInput`](/docs/api/payments-apps/latest/input-objects/VoidSessionRejectionReasonInput)
[`VoidSessionStatusReasonRejectionCode`](/docs/api/payments-apps/latest/enums/VoidSessionStatusReasonRejectionCode)
</Resources>
</Substep>
<Substep>
<CodeRef
framework="remix"
href="https://github.com/Shopify/example-app--credit-card-payments-app-template--remix/blob/docs/app/routes/app.dashboard_simulator.%24paymentId.jsx"
tag="build-credit-card-payments-app.dashboard-simulator.process-session"
/>
### Triggering resolve or reject for a void in the template
In the app template, the simulator built into the dashboard handles the resolution or rejection of all post-payment sessions, including voids, asynchronously. This means that after a void is created in a store's Shopify admin, it must be manually completed from the app template's dashboard.
In a production-ready app, your app would process the void itself after it receives the start void session request.
</Substep>
</Step>
<Step>
## Test your payments extension locally
Before submitting your extension for review, you should test that your various endpoints work as expected locally.
<Substep>
### Submit a request to your local server
With the dev server you started [previously](#start-your-development-server), go through the relevant requests from our [reference](/docs/apps/build/payments/request-reference), and submit a request to your app with `cURL` or an API platform like Postman or Insomnia.
</Substep>
</Step>
<Step>
## Submit your payments extension for review
After you've finished your draft, you can submit your payments extension for review. You can use this same process to submit new versions of your payments extension. Any changes that are made after publishing need to be approved by Shopify as a new version of the payments extension.
<Substep>
#### Deploy and release your extension
To get your app ready for review, create and release an app version.
1. Navigate to your app directory.
2. Run the following command.
Optionally, you can provide a name or message for the version using the `--version` and `--message` flags.
<Codeblock terminal>
```bash
shopify app deploy
```
</Codeblock>
---
An app version created using Shopify CLI contains the following:
- The app configuration from the local [configuration file](/docs/apps/build/app-configuration). If the `include_config_on_deploy` [flag](/docs/apps/build/app-configuration#build) is not set or `false`, then the configuration from the active app version will be used instead.
- The local version of the app's CLI-managed extensions. If you have an extension in your deployed app, but the extension code doesn't exist locally, then the extension isn't included in your app version.
- The latest drafts of dashboard-managed extensions.
Releasing an app version replaces the current active version that's served to stores with your app installed. It might take several minutes for app users to be upgraded to the new version.
<Notice type="tip">
If you want to create a version, but want to avoid releasing it to users, then run the `deploy` command with a `--no-release` flag.
You can release the unreleased app version using Shopify CLI's [`release`](/docs/api/shopify-cli/app/app-release) command, or through the Partner Dashboard.
</Notice>
</Substep>
<Substep>
### Submit the app version for review
Payments apps are [reviewed by Shopify](/docs/apps/build/payments/payments-extension-review) to ensure that they provide great merchant experience. Complete the following steps to submit your app version for review:
1. From the Partner Dashboard, go to [**Apps**](https://partners.shopify.com/current/apps).
1. Select your app from the list.
1. Click **Versions**.
1. Select the version that you want to release.
1. Click **Submit for review**.
</Substep>
<Substep>
### Release your app version
After Shopify has reviewed and approved the app version, you can release it to merchants.
1. From the Partner Dashboard, go to [**Apps**](https://partners.shopify.com/current/apps).
1. Click **Release** to release the new app version to users.
</Substep>
</Step>
<Step>
## Test your payments app with a Shop
Preview your app to make sure that it works as expected with Shopify.
<Notice type="info">
The testing steps outlined in this section are specific to apps built with the template. The template provides a basic UI that lets you test the payment flows, but your app might have a UI stored outside of the admin.
</Notice>
<Substep>
### Start your server
If you're using a permanent tunnel with your app extension, you can use the Shopify CLI `dev` command to build your app and preview it on your development store.
Otherwise, deploy your app to your server, and move to the next step.
1. In a terminal, navigate to your app directory.
1. Either start or restart your server to build and preview your app:
<Codeblock terminal>
```bash
shopify app dev --tunnel-url <tunnel>
```
</Codeblock>
1. Press `p` to open the developer console.
1. In the developer console page, click on the preview link for the app.
</Substep>
<Substep>
### Install and test the payments app
Follow these steps to test the payments app flows:
1. From the app splash page, enter an account name.
2. Select **Ready** > **Unstable** and click **Submit**.
3. In the banner, click **Return to Shopify**.
5. Enable test mode.
5. Click **Activate**.
6. You can select `Resolve` to complete the payment, or `Reject` to cancel and go back.
</Substep>
<Substep>
### Set up your payments app to accept test payments
Onboard your app onto your development store.
1. In the Shopify admin for your development store, go to **Settings** > [**Apps and sales channels**](https://admin.shopify.com/admin/settings/apps).
1. Select your payments app, then click **Open app**. The app home opens in a new window.
1. This step is applicable only to the template, if you've implemented the app yourself, complete your onboarding steps, and skip to the next step.
From the app home, enter an account name, select **Ready?** and your `Payments Apps API` **API Version**, and then click **Submit**.
In the banner, click **Return to Shopify**.
You'll return to admin, where you can review the app's details prior to activation.
<Troubleshooting>
##### An error ocurred while onboarding your app
Your app extension may not be set up properly. Ensure the URLs provided are accurate, and that the app extension is released. After this, you might have to uninstall and reinstall your payments app to successfully onboard. You can uninstall from your app's admin under **Settings** > [**Apps and sales channels**](https://admin.shopify.com/admin/settings/apps), and reinstall from your app's page in the Partner dashboard, under **Test your app**.
</Troubleshooting>
1. Enable test mode.
1. Click `Activate`.
Now that your app is installed, you can test payments, refunds, captures, voids, and 3-D Secure, if enabled.
---
<Resources>
[`Payments Apps API`](docs/api/payments-apps)
</Resources>
<Troubleshooting>
##### An error ocurred while onboarding your app
You might have to uninstall and reinstall your payments app. You can uninstall from your app's admin under **Settings** > [**Apps and sales channels**](https://admin.shopify.com/admin/settings/apps), and reinstall from your app's page in the Partner dashboard, under **Test your app**.
</Troubleshooting>
</Substep>
<Substep>
### Test payments
Make a payment, and create an order.
1. In your development store, add a product to your cart, and then begin a checkout.
1. Complete the checkout as usual, until the **Payment** section.
At this point, your payments app should be available under the **Credit card** section.
1. Enter some test credit card details, and then select **Pay now**.
Shopify sends a request to the `payment session URL` specified in your [app extension configuration](#configure-your-payments-apps-extension).
Your app should then begin, and complete processing the payment.
1. Verify the payment is complete by finding the order under [**Orders**](https://shopify.com/admin/orders) in your shop's admin.
</Substep>
<Substep>
### Test refunds
Make a refund on an order.
1. In the Shopify admin for your development store, go to [**Orders**](https://shopify.com/admin/orders). Select an order with a completed payment.
1. In the top right corner, click **Refund**.
1. Select the item to refund, and then click **Refund \<amount\>**.
Shopify sends a request to the `refund session URL` specified in your [app extension configuration](#configure-your-payments-extension).
If you've customized your app, then the refund process should trigger.
If you're testing the app template, then your app receives this request, and saves a record of it. Perform the following additional steps:
1. Navigate to `/app/dashboard` in your app to find the relevant payment session. From the app home, you can click **Dashboard**.
1. Click **Simulate**, and then scroll down to **Refunds**. On the relevant refund session, click **Open**.
1. Select **Resolve** or **Reject** to complete the refund.
1. Verify that the refund has completed by returning to the order under [**Orders**](https://shopify.com/admin/orders) in your store's admin. You should see that the order is now marked as **Refunded**.
</Substep>
<Substep>
### Test captures
Capture funds from an authorized payment.
1. In your development store, [enable manual payment capture](https://help.shopify.com/en/manual/payments/payment-authorization#set-up-manual-payment-capture) to test captures.
1. Submit another [test payment](#test-payments).
The order appears in the Shopify admin under [**Orders**](https://shopify.com/admin/orders).
1. Open the new order. In the top right corner, click **Capture payment**.
1. In the page that opens, click **Accept \<amount\>**.
Shopify sends a request to the `capture session URL` specified in your [app extension configuration](#configure-your-payments-apps-extension).
If you've customized your app, then the capture process should trigger.
If you're testing the app template, then your app receives this request, and saves a record of it. Perform the following additional steps:
1. Navigate to `/app/dashboard` in your app to find the relevant payment session. From the app home, you can click **Dashboard**.
1. Click **Simulate**, and then scroll down to **Captures**. On the relevant capture session, click **Open**.
1. Select **Resolve** or **Reject** to complete the capture.
1. Verify that the capture has completed by returning to the order under [**Orders**](https://shopify.com/admin/orders) in your store's admin. You should see that the order is now marked as **Paid**.
</Substep>
<Substep>
### Test voids
Void an authorized payment.
1. In your development store, [enable manual payment capture](https://help.shopify.com/en/manual/payments/payment-authorization#set-up-manual-payment-capture) to test voids.
1. Submit another [test payment](#test-payments).
The order appears in the Shopify admin under [**Orders**](https://shopify.com/admin/orders).
1. Open the new order. In the top right, click **More actions**, then **Cancel order**. In the modal that opens, click **Cancel order**.
Shopify sends a request to the `void session URL` specified in your [app extension configuration](#configure-your-payments-apps-extension).
If you've customized your app, then the void process should trigger.
If you're testing the app template, then your app receives this request, and saves a record of it. Perform the following additional steps:
1. Navigate to `/app/dashboard` in your app to find the relevant payment session. From the app home, you can click **Dashboard**.
1. Click **Simulate**, and then scroll down to **Void**. Click **Open** on the void session.
1. Select **Resolve** or **Reject** to complete the void.
1. Verify that the void has completed by returning to the order under [**Orders**](https://shopify.com/admin/orders) in your store's admin. You should see that the order is now marked as **Voided**.
</Substep>
<Substep>
### Test 3-D Secure
Complete a 3-D Secure challenge.
1. In your development store, add a product to your cart, and begin a checkout.
1. Complete the checkout as usual. If you're using the template, you can initiate the 3-D Secure flow by setting your first name to `3ds`.
If you want to try out the frictionless 3-D Secure experience offered by the template, you can enter the last name as `frictionless` during checkout.
1. When you reach the **Payment** section, your payments app should be available under the **Credit card** section.
1. Enter some test credit card details, and then select **Pay now**.
Shopify sends a request to the `payment session URL` specified in your [app extension configuration](#configure-your-payments-apps-extension). Your app should make a `paymentSessionRedirect` request at this point, which Shopify will respond to by opening an `iframe` in checkout with the provided URL.
1. The URL specified in your `paymentSessionRedirect` request is displayed in an `iframe`. In the template, you can select either **Authentication Data** or **Partner Error** and click **Submit**.
Your app should then send a `paymentSessionConfirm` request with the result of the 3-D Secure challenge. Shopify will respond with a request to the `confirm session URL` specified in your [app extension configuration](#configure-your-payments-apps-extension).
1. If the confirmation result is successful, then your app should begin, and then complete processing the payment. You can verify the payment is complete by finding the order under [**Orders**](https://shopify.com/admin/orders) in the Shopify admin.
1. If the confirmation result isn't successful, then your app should reject the payment.
To test a negative confirmation result (`confirmation_result=false`), do the following:
- Configure a product to [track inventory](https://help.shopify.com/manual/products/inventory/managing-inventory-quantities/track_inventory)
- Initiate a checkout with this product
- Proceed with a payment that triggers a 3DS challenge
- When the consumer is on the challenge, reset the inventory to 0
- Complete the challenge
The `paymentSessionConfirm` mutation should be called by your payments app and Shopify should then send a negative confirmation result (`confirmation_result=false`) to the app's `confirm session URL`.
</Substep>
<Substep>
### Test error scenarios
If you want to test out an error scenario in the template, then set the last name in a checkout to any `PaymentSessionStateRejectedReason`, and then complete the checkout as normal. The app template receives this code and automatically rejects the payment with it.
---
<Resources>
[`PaymentSessionStateRejectedReason`](/docs/api/payments-apps/latest/enums/PaymentSessionStateRejectedReason#top)
</Resources>
</Substep>
</Step>
</StepSection>
<NextSteps>
## Tutorial Complete!
Congratulations! You set up a credit card payments app using the our template.
### Next steps
<CardGrid>
<LinkCard href="/docs/apps/payments/credit-card/manage-encryption-certificates">
#### Manage encryption certificates
Your app requires encryption certificates to process credit card payments.
You can learn more about how to create and manage those certificates in this guide.
</LinkCard>
<LinkCard href="/docs/apps/build/payments/onboard-a-merchant-payments-extension">
#### Onboard a merchant
Before a merchant can use your payments app, they need to go through an onboarding experience.
This guide describes how a merchant discovers, installs, authorizes, and activates a payments app.
</LinkCard>
</CardGrid>
</NextSteps>