• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wp-graphql / wp-graphql-woocommerce / 23802452846

31 Mar 2026 02:24PM UTC coverage: 91.778% (+0.03%) from 91.753%
23802452846

push

github

web-flow
feat: Implements `refund` mutations (#906)

* chore: New feature stubbed out

* feat: Implement createRefund and deleteRefund mutations

createRefund: Creates a refund on an order with amount, reason,
optional payment gateway refund, restock, and meta data support.
Requires edit_shop_orders capability.

deleteRefund: Deletes a refund by ID with optional force flag.
Returns the deleted refund data and parent order.
Requires delete_shop_orders capability.

Both mutations include before/after action hooks for extensibility
and follow the WC REST API refund controller pattern.

Closes #17

* chore: Add PHPStan type annotation for wc_get_order in Refund_Delete

* devops: Add guard tests for deleteRefund mutation

Test invalid refund ID, order ID passed instead of refund ID,
with expectedErrorMessage assertions confirming error messages.

148 of 156 new or added lines in 4 files covered. (94.87%)

18484 of 20140 relevant lines covered (91.78%)

149.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.62
/includes/mutation/class-refund-create.php
1
<?php
2
/**
3
 * Mutation - createRefund
4
 *
5
 * Registers mutation for creating a refund on an order.
6
 *
7
 * @package WPGraphQL\WooCommerce\Mutation
8
 * @since TBD
9
 */
10

11
namespace WPGraphQL\WooCommerce\Mutation;
12

13
use GraphQL\Error\UserError;
14
use GraphQL\Type\Definition\ResolveInfo;
15
use WPGraphQL\AppContext;
16
use WPGraphQL\WooCommerce\Model\Order;
17

18
/**
19
 * Class Refund_Create
20
 */
21
class Refund_Create {
22
        /**
23
         * Registers mutation
24
         *
25
         * @return void
26
         */
27
        public static function register_mutation() {
28
                register_graphql_mutation(
291✔
29
                        'createRefund',
291✔
30
                        [
291✔
31
                                'inputFields'         => self::get_input_fields(),
291✔
32
                                'outputFields'        => self::get_output_fields(),
291✔
33
                                'mutateAndGetPayload' => self::mutate_and_get_payload(),
291✔
34
                        ]
291✔
35
                );
291✔
36
        }
37

38
        /**
39
         * Defines the mutation input field configuration.
40
         *
41
         * @return array
42
         */
43
        public static function get_input_fields() {
44
                return [
291✔
45
                        'orderId'       => [
291✔
46
                                'type'        => [ 'non_null' => 'Int' ],
291✔
47
                                'description' => static function () {
291✔
48
                                        return __( 'The ID of the order to refund.', 'wp-graphql-woocommerce' );
2✔
49
                                },
291✔
50
                        ],
291✔
51
                        'amount'        => [
291✔
52
                                'type'        => [ 'non_null' => 'String' ],
291✔
53
                                'description' => static function () {
291✔
54
                                        return __( 'Refund amount.', 'wp-graphql-woocommerce' );
2✔
55
                                },
291✔
56
                        ],
291✔
57
                        'reason'        => [
291✔
58
                                'type'        => 'String',
291✔
59
                                'description' => static function () {
291✔
60
                                        return __( 'Reason for refund.', 'wp-graphql-woocommerce' );
2✔
61
                                },
291✔
62
                        ],
291✔
63
                        'refundPayment' => [
291✔
64
                                'type'        => 'Boolean',
291✔
65
                                'description' => static function () {
291✔
66
                                        return __( 'When true, the payment gateway API is used to generate the refund.', 'wp-graphql-woocommerce' );
2✔
67
                                },
291✔
68
                        ],
291✔
69
                        'restockItems'  => [
291✔
70
                                'type'        => 'Boolean',
291✔
71
                                'description' => static function () {
291✔
72
                                        return __( 'When true, refunded items are restocked.', 'wp-graphql-woocommerce' );
2✔
73
                                },
291✔
74
                        ],
291✔
75
                        'metaData'      => [
291✔
76
                                'type'        => [ 'list_of' => 'MetaDataInput' ],
291✔
77
                                'description' => static function () {
291✔
78
                                        return __( 'Meta data.', 'wp-graphql-woocommerce' );
2✔
79
                                },
291✔
80
                        ],
291✔
81
                ];
291✔
82
        }
83

84
        /**
85
         * Defines the mutation output field configuration.
86
         *
87
         * @return array
88
         */
89
        public static function get_output_fields() {
90
                return [
291✔
91
                        'refund' => [
291✔
92
                                'type'    => 'Refund',
291✔
93
                                'resolve' => static function ( $payload ) {
291✔
94
                                        return new Order( $payload['id'] );
1✔
95
                                },
291✔
96
                        ],
291✔
97
                        'order'  => [
291✔
98
                                'type'    => 'Order',
291✔
99
                                'resolve' => static function ( $payload ) {
291✔
100
                                        return new Order( $payload['order_id'] );
1✔
101
                                },
291✔
102
                        ],
291✔
103
                ];
291✔
104
        }
105

106
        /**
107
         * Defines the mutation data modification closure.
108
         *
109
         * @return callable
110
         */
111
        public static function mutate_and_get_payload() {
112
                return static function ( $input, AppContext $context, ResolveInfo $info ) {
291✔
113
                        $order_id = absint( $input['orderId'] );
1✔
114
                        $order    = \wc_get_order( $order_id );
1✔
115

116
                        if ( ! $order ) {
1✔
NEW
117
                                throw new UserError( __( 'Invalid order ID.', 'wp-graphql-woocommerce' ) );
×
118
                        }
119

120
                        if ( ! \wc_rest_check_post_permissions( 'shop_order', 'edit', $order_id ) ) {
1✔
121
                                throw new UserError( __( 'You do not have permission to create refunds for this order.', 'wp-graphql-woocommerce' ) );
1✔
122
                        }
123

124
                        $amount = floatval( $input['amount'] );
1✔
125
                        if ( 0 >= $amount ) {
1✔
126
                                throw new UserError( __( 'Refund amount must be greater than zero.', 'wp-graphql-woocommerce' ) );
1✔
127
                        }
128

129
                        /**
130
                         * Action called before a refund is created.
131
                         *
132
                         * @param int                                  $order_id Order ID.
133
                         * @param array                                $input    Input data.
134
                         * @param \WPGraphQL\AppContext                $context  AppContext instance.
135
                         * @param \GraphQL\Type\Definition\ResolveInfo $info     ResolveInfo instance.
136
                         */
137
                        do_action( 'graphql_woocommerce_before_refund_create', $order_id, $input, $context, $info );
1✔
138

139
                        $refund = \wc_create_refund(
1✔
140
                                [
1✔
141
                                        'order_id'       => $order_id,
1✔
142
                                        'amount'         => $amount,
1✔
143
                                        'reason'         => ! empty( $input['reason'] ) ? $input['reason'] : null,
1✔
144
                                        'refund_payment' => ! empty( $input['refundPayment'] ) ? $input['refundPayment'] : false,
1✔
145
                                        'restock_items'  => ! empty( $input['restockItems'] ) ? $input['restockItems'] : false,
1✔
146
                                ]
1✔
147
                        );
1✔
148

149
                        if ( is_wp_error( $refund ) ) {
1✔
NEW
150
                                throw new UserError( $refund->get_error_message() );
×
151
                        }
152

153
                        if ( ! $refund ) {
1✔
NEW
154
                                throw new UserError( __( 'Could not create refund, please try again.', 'wp-graphql-woocommerce' ) );
×
155
                        }
156

157
                        // Set meta data.
158
                        if ( ! empty( $input['metaData'] ) && is_array( $input['metaData'] ) ) {
1✔
NEW
159
                                foreach ( $input['metaData'] as $meta ) {
×
NEW
160
                                        $refund->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
×
161
                                }
NEW
162
                                $refund->save_meta_data();
×
163
                        }
164

165
                        /**
166
                         * Action called after a refund is created.
167
                         *
168
                         * @param \WC_Order_Refund                     $refund   Refund object.
169
                         * @param int                                  $order_id Order ID.
170
                         * @param array                                $input    Input data.
171
                         * @param \WPGraphQL\AppContext                $context  AppContext instance.
172
                         * @param \GraphQL\Type\Definition\ResolveInfo $info     ResolveInfo instance.
173
                         */
174
                        do_action( 'graphql_woocommerce_after_refund_create', $refund, $order_id, $input, $context, $info );
1✔
175

176
                        return [
1✔
177
                                'id'       => $refund->get_id(),
1✔
178
                                'order_id' => $order_id,
1✔
179
                        ];
1✔
180
                };
291✔
181
        }
182
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc