--- title: Build a custom credit card payments extension description: Learn how to build a Shopify custom credit card payments extension. source_url: html: https://shopify.dev/docs/apps/build/payments/credit-card/custom/use-the-cli md: https://shopify.dev/docs/apps/build/payments/credit-card/custom/use-the-cli.md --- # Build a custom credit card payments extension **Plus:** This payment method is only available to be installed by Shopify Plus plans. Custom credit card payments extensions allow Partners to specify the hosted fields necessary to process their custom credit cards. You can collect essential information, such as installment details and required card numbers, directly on the checkout page before payment. This streamlines the checkout process and enhances the buyer experience. Custom credit card payments extensions are used primarily to help merchants accept their Private Label Credit Card (PLCC) on Shopify Checkout. ![An image of example custom credit card payment method](https://cdn.shopify.com/shopifycloud/shopify-dev/production/assets/assets/images/apps/payments/custom-credit-card/custom-credit-card-payment-method-app-example-BchUJCfB.png) **beta:** Processing a payment with a custom credit card payments extension is currently in an invite-only closed beta. *** ## What you'll learn **Note:** This document builds upon the [credit card payments extension tutorial](https://shopify.dev/docs/apps/build/payments/credit-card/use-the-cli), which you may refer to as needed for additional guidance. In this tutorial, you'll learn how to do the following tasks: * Create a checkout UI extension * Create a custom credit card payments extension * Explore the payment, refund, void, reject and capture session flows, and how to implement them yourself *** ## Requirements * Install [Shopify CLI](https://shopify.dev/docs/api/shopify-cli) * Create a [Shopify Partner account](https://www.shopify.com/partners) and apply to [become a payments partner](https://shopify.dev/docs/apps/build/payments/payments-extension-review#payments-partner-application-review) * Create a [dev store](https://shopify.dev/docs/apps/build/dev-dashboard/development-stores) * Create an [encryption certificate](https://shopify.dev/docs/apps/build/payments/credit-card/manage-encryption-certificates) **Note:** All Partners must submit a valid Payment Card Industry (PCI) Attestation of Compliance (AOC) before access to custom credit card payment extension is granted. *** ## Step 1: Scaffold an app To build a custom credit card payment extension you will need to create a new app, with a custom credit card payments extension and checkout UI extension correctly applied. Create your new app using the [Shopify CLI](https://shopify.dev/docs/apps/build/scaffold-app). **Tip:** Ensure you have the [latest version of Shopify CLI](https://shopify.dev/docs/api/shopify-cli#upgrade) installed to access all payment extension features. *** ## Step 2: Create a checkout UI extension After creating your app, generate a checkout UI extension and deploy your app to Shopify. This extension will be used to collect additional information that's required to process a payment. 1. Use the Shopify CLI to [scaffold](https://shopify.dev/docs/api/checkout-ui-extensions#scaffolding-extension) a checkout UI extension for your app. 2. Name your extension and choose to work in TypeScript Preact. **Note:** A sample extension, written in Typescript Preact, is shown below in the [Sample Checkout UI Extension](https://shopify.dev/docs/apps/build/payments/credit-card/custom/use-the-cli#sample-checkout-ui-extension) section. Please make sure to use the extension target and APIs as shown in the example. 3. After you generate the extension, [deploy](https://shopify.dev/docs/api/shopify-cli/app/app-deploy) your app. This will allow you to link the checkout UI extension with your payments app extension in the next section. 4. Navigate to your app in Shopify Partners (Apps > Your App). *** ## Step 3: Create a payments extension Your Shopify app becomes a payments app after you've created and configured your payments extension. 1. Run the following command to start generating your payments extension: ## Terminal ```bash shopify app generate extension ``` 2. When prompted for **Type of extension**, select **Payments extensions > Credit Card** and name your extension. *** ## Step 4: Configure your payments extension Configuration of custom credit card payments extension is similar to a [Credit Card payments extension](https://shopify.dev/docs/apps/build/payments/credit-card/use-the-cli). Your payments extension configures the following fields: | Property name | Description | | - | - | | `payment_session_url` required | The URL that receives payment and order details from the checkout. | | `refund_session_url` required | The URL that refund session requests are sent to. | | `capture_session_url` required | The URL that capture session requests are sent to. | | `void_session_url` required | The URL that void session requests are sent to. | | `confirmation_callback_url` optional | The URL that confirm session requests are sent to. This URL is required if your payments app supports 3-D Secure authentication. | | `supported_countries` required | The countries where your payments app is available. Refer to the [list of ISO 3166 (alpha-2) country codes](https://www.iso.org/iso-3166-country-codes.html) where your app is available for installation by merchants. Ensure the countries match the geographic requirements in your app listing when [submitting your app for review](https://shopify.dev/docs/apps/launch/app-store-review/submit-app-for-review#create-a-listing). | | `supports_3ds` required | 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` required | 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` required | Enables installments | | `supports_deferred_payments` required | Enables deferred payments | | `merchant_label` required | 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. | | `buyer_label` optional | The name of the payment method. Your checkout name can be the same as your merchant admin name or it can be customized for customers. This name is displayed with the payment methods that you support in the customer checkout. After a checkout name has been set, translations should be provided for localization. | | `test_mode_available` required | Enables merchants using your payments app to test their setup by simulating transactions. To test your app on a dev store, your payment provider in the Shopify admin must be set to test mode. | | `api_version` required | 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](https://shopify.dev/docs/api/usage/versioning). | | `multiple_capture` optional | 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` required | The certificate that Shopify uses to generate the ephemeral key and encrypt the credit card information of the customer. Refer to [manage encryption certificates](https://shopify.dev/docs/apps/build/payments/credit-card/manage-encryption-certificates) section to learn more. | | `ui_extension_handle` required | The UI extension that renders your payments app in checkout. This value can only be a UI extension linked to this specific payments app. | | `checkout_payment_method_fields` required | The fields your payments app will accept from buyers in checkout (for example, installment details, payment plan). Each field is composed of a key name, and a data type. The data type defines the input that a buyer can provide. | | `checkout_hosted_fields` required | The hosted fields your payments app will accept from buyers in checkout (for example, cardholder name, expiration date). This should be an array of string. | **Note:** The `ui_extension_handle`, `checkout_payment_method_fields` and `checkout_hosted_fields` attributes are new app extension configurations. You'll want to select the UI extension you created in **Create a Checkout UI Extension** as the value for the UI Extension field. The UI extension generated in [Create a checkout UI extension](https://shopify.dev/docs/apps/build/payments/credit-card/custom/use-the-cli#step-2-create-a-checkout-ui-extension) will determine what fields, validation, and form submission behavior is presented to buyers during checkout. ### UI Extension This is where you would identify the checkout extension you built prior to creating your payment app extension to tie them into one another. | Property name | Description | | - | - | | `ui_extension_handle` required | The UI extension that renders your payments app in checkout. This value can only be a UI extension linked to this specific payments app. | ### UI Extension Field Definitions The fields you are looking to accept from your UI extensions so the payment method can validate the correct data is sent from the front end. | Property name | Description | | - | - | | `checkout_payment_method_fields` required | The fields your payments app will accept from buyers in checkout (for example, installment details, payment plan). Each field is composed of a key name, and a data type. The data type defines the input that a buyer can provide. | ## Example UI extension field definitions ```text [[extensions.checkout_payment_method_fields]] key = "payment_plan" type = "string" required = true ``` ### Credit Card Fields | Property name | Description | | - | - | | `checkout_hosted_fields` required | The hosted fields your payments app will accept from buyers in checkout (for example, cardholder name, expiration date). This should be an array of string. | ```text # Supported values: ["name", "expiry", "verification_value", "issue_date", "issue_number"] checkout_hosted_fields = [ "name", "expiry", "verification_value" ] ``` The example field definitions configure a checkout form that appears as follows: ![An example image of custom credit card payments app](https://cdn.shopify.com/shopifycloud/shopify-dev/production/assets/assets/images/apps/payments/custom-credit-card/custom-credit-card-payment-method-app-example-BchUJCfB.png) *** ## Step 5: Set up your payments app ### Update your app configuration 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. The `write_payment_gateways` and `write_payment_sessions` scopes are automatically granted through the payment extension. Omit them from your `shopify.app.toml` file on initial deployment. In later deployments, you can add these scopes to request merchant permission for your payment extension. *** ## Step 6: Deploy your extension **Note:** The `write_payment_gateways` and `write_payment_sessions` scopes are automatically granted to your app through the payment extension. Therefore, on initial deployment of your app, you should omit the scopes from your `shopify.app.toml` file. However, you can include these scopes in later deployments, if you intend to use the scopes within the `shopify.app.toml` file to request permission from merchants to install your payment extension. Create and release an app version with the `deploy` command. 1. Navigate to your app directory. 2. Run the following command. ## Terminal ```terminal shopify app deploy ``` An app version created using Shopify CLI contains the following: * The app configuration from the local [configuration file](https://shopify.dev/docs/apps/build/cli-for-apps/app-configuration). * The local version of the app's 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. 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. **Note:** 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`](https://shopify.dev/docs/api/shopify-cli/app/app-release) command, or through the Dev Dashboard. *** ## Step 7: Start your development server 1. Start `app dev` if it's not already running: ## Terminal ```terminal shopify app dev ``` In your terminal, select your dev store. You can use the generated URL to test your payments app by using it in your [payments app configuration](#step-4-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. 2. Press `p` to open the app in your browser. This brings you to your development store's admin. *** ## Step 8: Test your payments app with a store Once this version has been released, follow these steps to install your app on your dev store: 1. From the app splash page, enter an account name. 2. Select **Ready** < **Unstable** and click **Submit**. You need to set your app as ready to process payments by calling the [`paymentsAppConfigure`](https://shopify.dev/docs/api/payments-apps/latest/mutations/paymentsAppConfigure) mutation. ## POST https://{shop\_domain}/payments\_apps/api/{api\_version}/graphql.json ## Mutation ```graphql mutation PaymentsAppConfigure($externalHandle: String, $ready: Boolean!) { paymentsAppConfigure(externalHandle: $externalHandle, ready: $ready) { userErrors{ field message } } } ``` There are two required arguments that you need to provide in the mutation: * `externalHandle`: A account name or identifier associated with the account that the merchant has used with the Partner. * `ready`: A signal that the provider is ready to process payments. Accepted values are `true` or `false`. * `true`: Allows your app to process payments. * `false`: Indicates your app isn't yet able to process payments. **Caution:** If you set `ready: false` after the app is installed and activated on the store, the request will fail. 1. In the banner, click **Return to Shopify**. After calling the mutation, you must configure your app to redirect back to the Shopify admin using the following URL: https://{shop}.myshopify.com/services/payments\_partners/gateways/${api\_key}/settings 2. Enable test mode. 3. Click **Activate**. *** ## Explore the payment processing flows The Payments App functions similarly to the Credit Card payment method, allowing you to gather additional information from the buyer at the outset to facilitate payment processing. The primary distinction is that checkout UI extension data is included in the `start_payment_session` body. Outlined below is a comprehensive diagram depicting the potential flow for processing a custom credit card payment. It's important to note that the pending state is optional; you can directly proceed to either resolve or reject the payment if there is no need to place it in a pending state. For further details on processing credit card payments, please refer to this [resource](https://shopify.dev/docs/apps/build/payments/credit-card/use-the-cli). ![An image of custom credit card payment flow](https://cdn.shopify.com/shopifycloud/shopify-dev/production/assets/assets/images/apps/payments/credit-card-payment-method-payment-flow-BZQzuiX3.png) Once we start the payment session with your payments app, that initiation will also contain the metadata in a shape similar to what was specified within the field definitions. A [sample payment session payload](https://shopify.dev/docs/apps/build/payments/credit-card/custom/use-the-cli#payment-session) of what is to be expected can be seen below. ### Payment session Payments with payments apps are processed asynchronously. When the buyer completes their checkout, a request will be sent from Shopify to the *Payment session URL* defined in [Configure your payments app extension](https://shopify.dev/docs/apps/build/payments/credit-card/custom/use-the-cli#step-4-configure-your-payments-extension), with the checkout and payment details. The Payments app should respond with HTTP 2xx to indicate that the payment session was started, and should begin processing the custom credit card payment at this point. The new metadata we are passing through payment session would be contained within the payment\_method request params under attributes: ## Example payment method request parameters ```json "payment_method":{ "type":"credit_card" "data":{ "attributes": [ { "key": "payment_plan", "value": "pay-in-full" } ], }, }, ``` ## Start session ## Request header ```http Shopify-Shop-Domain: my-test-shop.myshopify.com Shopify-Request-Id: 94169f7e-ac8d-4ef4-9fd2-90f0791daddf Shopify-Api-Version: 2026-01 ``` ## Request body ```json { "id": "u0nwmSrNntjIWozmNslK5Tlq", "gid": "gid://shopify/PaymentSession/u0nwmSrNntjIWozmNslK5Tlq", "group": "rZNvy+1jH6Z+BcPqA5U5BSIcnUavBha3C63xBalm+xE=", "session_id": "4B2dxmle3vGgimS4deUX3+2PgLF2+/0ZWnNsNSZcgdU=", "amount": "123.00", "currency": "CAD", "test": false, "merchant_locale": "en", "payment_method": { "type": "credit_card", "data": { "attributes": [ { "key": "payment_plan", "value": "pay-in-full" } ], "fingerprint": "65b1ae1fe49ff9d23d80e4967d9147e64b2357c0b2291f4a8bf719cbde331b4c", "encrypted_message": "", "ephemeral_public_key": "", "tag": "TAG", "moto": true } }, "proposed_at": "2020-07-13T00:00:00Z", "customer": { "billing_address": { "given_name": "Alice", "family_name": "Smith", "line1": "123 Street", "line2": "Suite B", "city": "Montreal", "postal_code": "H2Z 0B3", "province": "Quebec", "country_code": "CA", "phone_number": "5555555555", "company": "" }, "shipping_address": { "given_name": "Alice", "family_name": "Smith", "line1": "123 Street", "line2": "Suite B", "city": "Montreal", "postal_code": "H2Z 0B3", "province": "Quebec", "country_code": "CA", "phone_number": "5555555555", "company": "" }, "email": "buyer@example.com", "phone_number": "5555555555", "locale": "fr" }, "kind": "sale" } ``` ## Response body ```text Our API expects an empty response body. ``` #### Resolve Once the payments app has responded to the initial start payment session request, it should begin processing the payment. Since this is an asynchronous process, the payments app will be performing the next step independently, through the [paymentSessionResolve](https://shopify.dev/docs/api/payments-apps/2023-07/mutations/paymentSessionResolve) mutation on the [Payments Apps GraphQL API](https://shopify.dev/docs/api/payments-apps). This mutation will resolve the payment session, indicating that the payment was successful. Example GraphQL mutation: ## POST https://{shop}.myshopify.com/payments\_apps/api/unstable/graphql.json ## Mutation ```graphql mutation PaymentSessionResolve($id: ID!) { paymentSessionResolve(id: $id) { paymentSession { id state { ... on PaymentSessionStateResolved { code } } } userErrors { field message } } } ``` ## Input variables ```json { "id": "gid://shopify/PaymentSession/reItEndH7tKB4sGkjronhdEgv" } ``` ## JSON response ```json { "data": { "paymentSessionResolve": { "paymentSession": { "id": "gid://shopify/PaymentSession/reItEndH7tKB4sGkjronhdEgv", "state": { "code": "RESOLVED" } }, "userErrors": [] } } } ``` After this, the payment will be marked as resolved in Shopify. #### Reject If a payment was unsuccessful for any reason, the payments app must use the [paymentSessionReject](https://shopify.dev/docs/api/payments-apps/2023-07/mutations/paymentSessionReject) mutation. ## POST https://{shop}.myshopify.com/payments\_apps/api/unstable/graphql.json ## Mutation ```graphql mutation PaymentSessionReject( $id: ID!, $reason: PaymentSessionRejectionReasonInput! ) { paymentSessionReject( id: $id, reason: $reason ) { paymentSession { id state { ... on PaymentSessionStateRejected { code reason merchantMessage } } } userErrors { field message } } } ``` ## Input variables ```json { "id": "gid://shopify/PaymentSession/reItEndH7tKB4sGkjronhdEgv", "reason": { "code": "PROCESSING_ERROR", "merchantMessage": "the payment didn't work" } } ``` ## JSON response ```json { "data": { "paymentSessionReject": { "paymentSession": { "id": "gid://shopify/PaymentSession/reItEndH7tKB4sGkjronhdEgv", "state": { "code": "REJECTED", "reason": "PROCESSING_ERROR", "merchantMessage": "the payment didn't work" } }, "userErrors": [] } } } ``` #### Error These are the [reasons](https://shopify.dev/docs/api/payments-apps/2024-04/enums/PaymentSessionStateRejectedReason) you can reject a payment session with. ### 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. You can read more about refund sessions and how to reject or resolve refund sessions [here](https://shopify.dev/docs/apps/build/payments/credit-card/use-the-cli?framework=remix#explore-refund-sessions). Example request body: ## Example request body ```json "request_params": { "id": "reItEndH7tKB4sGkjronhdEgv", "gid": "gid://shopify/RefundSession/reItEndH7tKB4sGkjronhdEgv", "payment_id": "reItEndH7tKB4sGkjronhdEgv", "amount": "39.90", "currency": "CAD", "merchant_locale": "en", "proposed_at": "2024-04-22T17:10:03Z", "test": true } ``` #### Resolve After the app successfully processes the refund request, the [`refundSessionResolve`](https://shopify.dev/docs/api/payments-apps/latest/mutations/refundSessionResolve) mutation automatically resolves the refund. The `id` argument corresponds to the `gid` of the refund. Example GraphQL mutation: ## POST https://{shop}.myshopify.com/payments\_apps/api/unstable/graphql.json ## Mutation ```graphql mutation refundSessionResolve($id: ID!) { refundSessionResolve(id: $id) { refundSession { # RefundSession fields } userErrors { field message } } } ``` ## Input variables ```json { "id": "gid://shopify/RefundSession/rh60PS44WpmEgki4D6IK1Mu63" } ``` ## JSON response ```json { "data": { "refundSessionResolve": { "refundSession": { "id": "gid://shopify/RefundSession/reItEndH7tKB4sGkjronhdEgv", "state": { "code": "RESOLVED" } }, "userErrors": [] } } } ``` After this, the refund will be marked as resolved in Shopify. #### Reject 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](https://shopify.dev/docs/api/payments-apps/2023-07/mutations/refundSessionReject) mutation. As part of the rejection, a reason why the refund was rejected must be included as part of `RefundSessionRejectionReasonInput`. Example GraphQL mutation: ## POST https://{shop}.myshopify.com/payments\_apps/api/unstable/graphql.json ## Mutation ```graphql mutation refundSessionReject( $id: ID!, $reason: RefundSessionRejectionReasonInput! ) { refundSessionReject( id: $id, reason: $reason ) { refundSession { # RefundSession fields } userErrors { field message } } } ``` ## Input variables ```json { "id": "gid://shopify/RefundSession/reItEndH7tKB4sGkjronhdEgv", "reason": { "code": "PROCESSING_ERROR", "merchantMessage": "the payment didn't work" } } ``` ## JSON response ```json { "data": { "refundSessionReject": { "refundSession": { "id": "gid://shopify/RefundSession/reItEndH7tKB4sGkjronhdEgv", "state": { "code": "REJECTED", "reason": "PROCESSING_ERROR", "merchantMessage": "the refund didn't work" } }, "userErrors": [] } } } ``` ### 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 can read more about capture sessions and how to reject or resolve capture sessions [here](https://shopify.dev/docs/apps/payments/credit-card/use-the-cli?framework=remix#explore-capture-sessions). Example request body: ## Example request body ```json "request_params": { "id": "reItEndH7tKB4sGkjronhdEgv", "gid": "gid://shopify/CaptureSession/reItEndH7tKB4sGkjronhdEgv", "payment_id": "reItEndH7tKB4sGkjronhdEgv", "amount": "39.90", "currency": "CAD", "merchant_locale": "en", "proposed_at": "2024-04-22T17:10:03Z", "test": true } ``` #### Resolve After the app successfully processes the capture request, the [`captureSessionResolve`](https://shopify.dev/docs/api/payments-apps/latest/mutations/captureSessionResolve) mutation automatically resolves the capture. The `id` argument corresponds to the `gid` of the capture. Example GraphQL mutation: ## POST https://{shop}.myshopify.com/payments\_apps/api/unstable/graphql.json ## Mutation ```graphql mutation captureSessionResolve($id: ID!) { captureSessionResolve(id: $id) { captureSession { # CaptureSession fields } userErrors { field message } } } ``` ## Input variables ```json { "id": "gid://shopify/CaptureSession/rh60PS44WpmEgki4D6IK1Mu63" } ``` ## JSON response ```json { "data": { "captureSessionResolve": { "captureSession": { "id": "gid://shopify/CaptureSession/rh60PS44WpmEgki4D6IK1Mu63", "state": { "code": "RESOLVED" } }, "userErrors": [] } } } ``` After this, the capture will be marked as resolved in Shopify. #### Reject 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`](https://shopify.dev/docs/api/payments-apps/latest/mutations/captureSessionReject) mutation. As part of the rejection, you need to include a reason why the capture was rejected as part of `CaptureSessionRejectionReasonInput`. Example GraphQL mutation: ## POST https://{shop}.myshopify.com/payments\_apps/api/unstable/graphql.json ## Mutation ```graphql mutation captureSessionReject( $id: ID!, $reason: CaptureSessionRejectionReasonInput! ) { captureSessionReject( id: $id, reason: $reason ) { captureSession { # CaptureSession fields } userErrors { field message } } } ``` ## Input variables ```json { "id": "gid://shopify/CaptureSession/reItEndH7tKB4sGkjronhdEgv", "reason": { "code": "AUTHORIZATION_EXPIRED", "merchantMessage": "the authorization didn't work" } } ``` ## JSON response ```json { "data": { "captureSessionReject": { "captureSession": { "id": "gid://shopify/CaptureSession/reItEndH7tKB4sGkjronhdEgv", "state": { "code": "REJECTED", "reason": "AUTHORIZATION_EXPIRED", "merchantMessage": "the authorization didn't work" } }, "userErrors": [] } } } ``` ### 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 can read more about void sessions and how to reject or resolve void sessions [here](https://shopify.dev/docs/apps/payments/credit-card/use-the-cli?framework=remix#explore-void-sessions). Example request body: ## Example request body ```json "request_params": { "id": "reItEndH7tKB4sGkjronhdEgv", "gid": "gid://shopify/VoidSession/reItEndH7tKB4sGkjronhdEgv", "payment_id": "reItEndH7tKB4sGkjronhdEgv", "amount": "39.90", "currency": "CAD", "merchant_locale": "en", "proposed_at": "2024-04-22T17:10:03Z", "test": true } ``` #### Resolve After the app successfully processes the void request, the [`voidSessionResolve`](https://shopify.dev/docs/api/payments-apps/latest/mutations/voidSessionResolve) mutation automatically resolves the void. The `id` argument corresponds to the `gid` of the void. Example GraphQL mutation: ## POST https://{shop}.myshopify.com/payments\_apps/api/unstable/graphql.json ## Mutation ```graphql mutation voidSessionResolve($id: ID!) { voidSessionResolve(id: $id) { voidSession { # VoidSession fields } userErrors { field message } } } ``` ## Input variables ```json { "id": "gid://shopify/VoidSession/rh60PS44WpmEgki4D6IK1Mu63" } ``` ## JSON response ```json { "data": { "voidSessionResolve": { "voidSession": { "id": "gid://shopify/VoidSession/rh60PS44WpmEgki4D6IK1Mu63", "state": { "code": "RESOLVED" } }, "userErrors": [] } } } ``` After this, the void will be marked as resolved in Shopify. #### Reject 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](https://shopify.dev/docs/api/payments-apps/2023-07/mutations/voidSessionReject) mutation. As part of the rejection, you need to include a reason why the void was rejected as part of `VoidSessionRejectionReasonInput`. Example GraphQL mutation: ## POST https://{shop}.myshopify.com/payments\_apps/api/unstable/graphql.json ## Mutation ```graphql mutation voidSessionReject( $id: ID!, $reason: VoidSessionRejectionReasonInput! ) { voidSessionReject( id: $id, reason: $reason ) { voidSession { # VoidSession fields } userErrors { field message } } } ``` ## Input variables ```json { "id": "gid://shopify/VoidSession/reItEndH7tKB4sGkjronhdEgv", "reason": { "code": "PROCESSING_ERROR", "merchantMessage": "the void didn't work" } } ``` ## JSON response ```json { "data": { "voidSessionReject": { "voidSession": { "id": "gid://shopify/VoidSession/reItEndH7tKB4sGkjronhdEgv", "state": { "code": "REJECTED", "reason": "PROCESSING_ERROR", "merchantMessage": "the void didn't work" } }, "userErrors": [] } } } ``` *** ## Sample Checkout UI Extension **Note:** Update to the latest version of checkout UI extensions so you can use [Polaris web components](https://shopify.dev/docs/api/polaris). The legacy `checkout-ui-extensions-react` package is no longer required. The following example demonstrates a checkout UI extension for a custom credit card payments app. It uses hooks, such as `useApplyPaymentMethodAttributesChange` and `usePaymentMethodAttributeValues` from the Preact-specific package. ## extensions/\/src/Checkout.jsx ```ts import '@shopify/ui-extensions/preact'; import {render} from "preact"; import { useApplyPaymentMethodAttributesChange, usePaymentMethodAttributeValues, } from '@shopify/ui-extensions/checkout/preact'; export default async () => { render(, document.body) }; const LOG_PREFIX = '[Checkout UI Extension]'; function Extension() { const [paymentPlanValue] = usePaymentMethodAttributeValues(['payment_plan']); const applyPaymentMethodAttributesChange = useApplyPaymentMethodAttributesChange(); function apply({paymentPlan = paymentPlanValue}) { /** @type {{type: 'updatePaymentMethodAttributes', attributes: Array<{key: string, value: string}>}} */ const change = { type: 'updatePaymentMethodAttributes', attributes: [{key: 'payment_plan', value: String(paymentPlan)}], }; applyPaymentMethodAttributesChange(change) .then((result) => { console.log(`${LOG_PREFIX} Applied change`, change, result); }) .catch((error) => { console.error(`${LOG_PREFIX} Failed to apply`, change, error); }); } return ( {}}> apply({ paymentPlan: /** @type {any} */ (event.currentTarget).value })} value={String(paymentPlanValue)} > Pay in full Pay Half ); } ``` **Note:** You must specify the extension point you're rendering to (`purchase.checkout.payment-option-item.hosted-fields.render-after`) in the `shopify.extension.toml` file in your extension directory. The [standardAPI](https://shopify.dev/docs/api/checkout-ui-extensions/latest/targets) is a good starting point to see what is available for your checkout extension. A list of available components can be found [here](https://shopify.dev/docs/api/checkout-ui-extensions/latest/components). *** ## Frequently asked questions 1. Does the custom credit card extension accommodate split payments or installments? Custom credit card extension provides partners with the flexibility to include a field in the extension, showcasing various payment options to buyers. However, it's important to note that Shopify processes these as a single, full payment to the processor. Any arrangements for payment schedules or installments are then managed directly between the processor and the buyer, without Shopify's involvement. *** ## Tutorial complete! Congratulations! You set up a custom credit card payments app. ***